[mdns] implement NsdPublisher AIDL service
For ot-daemon to perform mDNS operations, we need to implement an AIDL
service NsdPublisher. MdnsPublisher forwards the calls to NsdManager.
For now, only register & unregster a service without a custom host is supported.
Bug: 320211657
Bug: 318323473
Test: atest CtsThreadNetworkTestCases
Change-Id: Ia392a0f57046f53973ac6bd6043dec915d170666
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 81e24da..522120c 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -51,4 +51,5 @@
// Test coverage system runs on different devices. Need to
// compile for all architectures.
compile_multilib: "both",
+ platform_apis: true,
}
diff --git a/thread/tests/cts/AndroidManifest.xml b/thread/tests/cts/AndroidManifest.xml
index 4370fe3..1541bf5 100644
--- a/thread/tests/cts/AndroidManifest.xml
+++ b/thread/tests/cts/AndroidManifest.xml
@@ -19,6 +19,9 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="android.net.thread.cts">
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
+
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index aab4b2e..3bec36b 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -17,6 +17,7 @@
package android.net.thread.cts;
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_ROUTER;
@@ -32,6 +33,7 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.truth.Truth.assertThat;
@@ -46,9 +48,12 @@
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.LinkAddress;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.OperationalDatasetTimestamp;
import android.net.thread.PendingOperationalDataset;
@@ -60,6 +65,7 @@
import android.os.Build;
import android.os.OutcomeReceiver;
+import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
@@ -68,6 +74,7 @@
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.FunctionalUtils.ThrowingRunnable;
+import com.android.testutils.TestNetworkTracker;
import org.junit.After;
import org.junit.Before;
@@ -75,16 +82,22 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Random;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Predicate;
/** CTS tests for {@link ThreadNetworkController}. */
@LargeTest
@@ -97,6 +110,8 @@
private static final int NETWORK_CALLBACK_TIMEOUT_MILLIS = 10 * 1000;
private static final int CALLBACK_TIMEOUT_MILLIS = 1_000;
private static final int ENABLED_TIMEOUT_MILLIS = 2_000;
+ private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 10 * 1000;
+ private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
private static final String THREAD_NETWORK_PRIVILEGED =
"android.permission.THREAD_NETWORK_PRIVILEGED";
@@ -105,6 +120,7 @@
private final Context mContext = ApplicationProvider.getApplicationContext();
private ExecutorService mExecutor;
private ThreadNetworkController mController;
+ private NsdManager mNsdManager;
private Set<String> mGrantedPermissions;
@@ -123,6 +139,8 @@
assumeNotNull(mController);
setEnabledAndWait(mController, true);
+
+ mNsdManager = mContext.getSystemService(NsdManager.class);
}
@After
@@ -809,6 +827,74 @@
getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(allPermissions);
}
+ @Test
+ public void meshcopService_threadEnabledButNotJoined_discoveredButNoNetwork() throws Exception {
+ TestNetworkTracker testNetwork = setUpTestNetwork();
+
+ setEnabledAndWait(mController, true);
+ leaveAndWait(mController);
+
+ NsdServiceInfo serviceInfo =
+ expectServiceResolved(
+ MESHCOP_SERVICE_TYPE,
+ SERVICE_DISCOVERY_TIMEOUT_MILLIS,
+ s -> s.getAttributes().get("at") == null);
+
+ Map<String, byte[]> txtMap = serviceInfo.getAttributes();
+
+ assertThat(txtMap.get("rv")).isNotNull();
+ assertThat(txtMap.get("tv")).isNotNull();
+ assertThat(txtMap.get("sb")).isNotNull();
+
+ tearDownTestNetwork(testNetwork);
+ }
+
+ @Test
+ public void meshcopService_joinedNetwork_discoveredHasNetwork() throws Exception {
+ TestNetworkTracker testNetwork = setUpTestNetwork();
+
+ String networkName = "TestNet" + new Random().nextInt(10_000);
+ joinRandomizedDatasetAndWait(mController, networkName);
+
+ Predicate<NsdServiceInfo> predicate =
+ serviceInfo ->
+ serviceInfo.getAttributes().get("at") != null
+ && Arrays.equals(
+ serviceInfo.getAttributes().get("nn"),
+ networkName.getBytes(StandardCharsets.UTF_8));
+
+ NsdServiceInfo resolvedService =
+ expectServiceResolved(
+ MESHCOP_SERVICE_TYPE, SERVICE_DISCOVERY_TIMEOUT_MILLIS, predicate);
+
+ Map<String, byte[]> txtMap = resolvedService.getAttributes();
+ assertThat(txtMap.get("rv")).isNotNull();
+ assertThat(txtMap.get("tv")).isNotNull();
+ assertThat(txtMap.get("sb")).isNotNull();
+ assertThat(txtMap.get("id").length).isEqualTo(16);
+
+ tearDownTestNetwork(testNetwork);
+ }
+
+ @Test
+ public void meshcopService_threadDisabled_notDiscovered() throws Exception {
+ TestNetworkTracker testNetwork = setUpTestNetwork();
+
+ CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
+ NsdManager.DiscoveryListener listener =
+ discoverForServiceLost(MESHCOP_SERVICE_TYPE, serviceLostFuture);
+ setEnabledAndWait(mController, false);
+
+ try {
+ serviceLostFuture.get(10_000, MILLISECONDS);
+ } finally {
+ mNsdManager.stopServiceDiscovery(listener);
+ }
+ assertThrows(TimeoutException.class, () -> discoverService(MESHCOP_SERVICE_TYPE));
+
+ tearDownTestNetwork(testNetwork);
+ }
+
private static void dropAllPermissions() {
getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
}
@@ -888,6 +974,12 @@
runAsShell(THREAD_NETWORK_PRIVILEGED, () -> controller.leave(mExecutor, receiver));
}
+ private void leaveAndWait(ThreadNetworkController controller) throws Exception {
+ CompletableFuture<Void> future = new CompletableFuture<>();
+ leave(controller, future::complete);
+ future.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+ }
+
private void scheduleMigration(
ThreadNetworkController controller,
PendingOperationalDataset pendingDataset,
@@ -942,9 +1034,9 @@
waitForEnabledState(controller, booleanToEnabledState(enabled));
}
- private CompletableFuture joinRandomizedDataset(ThreadNetworkController controller)
- throws Exception {
- ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
+ private CompletableFuture joinRandomizedDataset(
+ ThreadNetworkController controller, String networkName) throws Exception {
+ ActiveOperationalDataset activeDataset = newRandomizedDataset(networkName, controller);
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
runAsShell(
THREAD_NETWORK_PRIVILEGED,
@@ -953,7 +1045,12 @@
}
private void joinRandomizedDatasetAndWait(ThreadNetworkController controller) throws Exception {
- CompletableFuture<Void> joinFuture = joinRandomizedDataset(controller);
+ joinRandomizedDatasetAndWait(controller, "TestNet");
+ }
+
+ private void joinRandomizedDatasetAndWait(
+ ThreadNetworkController controller, String networkName) throws Exception {
+ CompletableFuture<Void> joinFuture = joinRandomizedDataset(controller, networkName);
joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
assertThat(isAttached(controller)).isTrue();
}
@@ -1010,4 +1107,103 @@
fail("Should not have thrown " + e);
}
}
+
+ // Return the first discovered service instance.
+ private NsdServiceInfo discoverService(String serviceType) throws Exception {
+ CompletableFuture<NsdServiceInfo> serviceInfoFuture = new CompletableFuture<>();
+ NsdManager.DiscoveryListener listener =
+ new DefaultDiscoveryListener() {
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {
+ serviceInfoFuture.complete(serviceInfo);
+ }
+ };
+ mNsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+ try {
+ serviceInfoFuture.get(SERVICE_DISCOVERY_TIMEOUT_MILLIS, MILLISECONDS);
+ } finally {
+ mNsdManager.stopServiceDiscovery(listener);
+ }
+
+ return serviceInfoFuture.get();
+ }
+
+ private NsdManager.DiscoveryListener discoverForServiceLost(
+ String serviceType, CompletableFuture<NsdServiceInfo> serviceInfoFuture) {
+ NsdManager.DiscoveryListener listener =
+ new DefaultDiscoveryListener() {
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {
+ serviceInfoFuture.complete(serviceInfo);
+ }
+ };
+ mNsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
+ return listener;
+ }
+
+ private NsdServiceInfo expectServiceResolved(
+ String serviceType, int timeoutMilliseconds, Predicate<NsdServiceInfo> predicate)
+ throws Exception {
+ NsdServiceInfo discoveredServiceInfo = discoverService(serviceType);
+ CompletableFuture<NsdServiceInfo> future = new CompletableFuture<>();
+ NsdManager.ServiceInfoCallback callback =
+ new DefaultServiceInfoCallback() {
+ @Override
+ public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {
+ if (predicate.test(serviceInfo)) {
+ future.complete(serviceInfo);
+ }
+ }
+ };
+ mNsdManager.registerServiceInfoCallback(discoveredServiceInfo, mExecutor, callback);
+ try {
+ return future.get(timeoutMilliseconds, MILLISECONDS);
+ } finally {
+ mNsdManager.unregisterServiceInfoCallback(callback);
+ }
+ }
+
+ TestNetworkTracker setUpTestNetwork() {
+ return runAsShell(
+ MANAGE_TEST_NETWORKS,
+ () -> initTestNetwork(mContext, new LinkAddress("2001:db8:123::/64"), 10_000));
+ }
+
+ void tearDownTestNetwork(TestNetworkTracker testNetwork) {
+ runAsShell(MANAGE_TEST_NETWORKS, () -> testNetwork.teardown());
+ }
+
+ private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener {
+ @Override
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {}
+
+ @Override
+ public void onDiscoveryStarted(String serviceType) {}
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {}
+
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {}
+
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {}
+ }
+
+ private static class DefaultServiceInfoCallback implements NsdManager.ServiceInfoCallback {
+ @Override
+ public void onServiceInfoCallbackRegistrationFailed(int errorCode) {}
+
+ @Override
+ public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {}
+
+ @Override
+ public void onServiceLost() {}
+
+ @Override
+ public void onServiceInfoCallbackUnregistered() {}
+ }
}
diff --git a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
new file mode 100644
index 0000000..8aea0a3
--- /dev/null
+++ b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2024 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.thread;
+
+import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
+import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import com.android.server.thread.openthread.DnsTxtAttribute;
+import com.android.server.thread.openthread.INsdStatusReceiver;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/** Unit tests for {@link NsdPublisher}. */
+public final class NsdPublisherTest {
+ @Mock private NsdManager mMockNsdManager;
+
+ @Mock private INsdStatusReceiver mRegistrationReceiver;
+ @Mock private INsdStatusReceiver mUnregistrationReceiver;
+
+ private TestLooper mTestLooper;
+ private NsdPublisher mNsdPublisher;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void registerService_nsdManagerSucceeds_serviceRegistrationSucceeds() throws Exception {
+ prepareTest();
+
+ DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+ DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService",
+ "_test._tcp",
+ List.of("_subtype1", "_subtype2"),
+ 12345,
+ List.of(txt1, txt2),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+ mTestLooper.dispatchAll();
+
+ assertThat(actualServiceInfo.getServiceName()).isEqualTo("MyService");
+ assertThat(actualServiceInfo.getServiceType()).isEqualTo("_test._tcp");
+ assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
+ assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
+ assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
+ assertThat(actualServiceInfo.getAttributes().get("key1"))
+ .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
+ assertThat(actualServiceInfo.getAttributes().get("key2"))
+ .isEqualTo(new byte[] {(byte) 0x03});
+
+ verify(mRegistrationReceiver, times(1)).onSuccess();
+ }
+
+ @Test
+ public void registerService_nsdManagerFails_serviceRegistrationFails() throws Exception {
+ prepareTest();
+
+ DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+ DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService",
+ "_test._tcp",
+ List.of("_subtype1", "_subtype2"),
+ 12345,
+ List.of(txt1, txt2),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onRegistrationFailed(actualServiceInfo, FAILURE_INTERNAL_ERROR);
+ mTestLooper.dispatchAll();
+
+ assertThat(actualServiceInfo.getServiceName()).isEqualTo("MyService");
+ assertThat(actualServiceInfo.getServiceType()).isEqualTo("_test._tcp");
+ assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
+ assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
+ assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
+ assertThat(actualServiceInfo.getAttributes().get("key1"))
+ .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
+ assertThat(actualServiceInfo.getAttributes().get("key2"))
+ .isEqualTo(new byte[] {(byte) 0x03});
+
+ verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
+ }
+
+ @Test
+ public void registerService_nsdManagerThrows_serviceRegistrationFails() throws Exception {
+ prepareTest();
+
+ DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+ DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+ doThrow(new IllegalArgumentException("NsdManager fails"))
+ .when(mMockNsdManager)
+ .registerService(any(), anyInt(), any(Executor.class), any());
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService",
+ "_test._tcp",
+ List.of("_subtype1", "_subtype2"),
+ 12345,
+ List.of(txt1, txt2),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
+ }
+
+ @Test
+ public void unregisterService_nsdManagerSucceeds_serviceUnregistrationSucceeds()
+ throws Exception {
+ prepareTest();
+
+ DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+ DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService",
+ "_test._tcp",
+ List.of("_subtype1", "_subtype2"),
+ 12345,
+ List.of(txt1, txt2),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+ mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+ verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
+
+ actualRegistrationListener.onServiceUnregistered(actualServiceInfo);
+ mTestLooper.dispatchAll();
+ verify(mUnregistrationReceiver, times(1)).onSuccess();
+ }
+
+ @Test
+ public void unregisterService_nsdManagerFails_serviceUnregistrationFails() throws Exception {
+ prepareTest();
+
+ DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+ DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService",
+ "_test._tcp",
+ List.of("_subtype1", "_subtype2"),
+ 12345,
+ List.of(txt1, txt2),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+
+ NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
+ NsdManager.RegistrationListener actualRegistrationListener =
+ actualRegistrationListenerCaptor.getValue();
+
+ actualRegistrationListener.onServiceRegistered(actualServiceInfo);
+ mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+ verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
+
+ actualRegistrationListener.onUnregistrationFailed(
+ actualServiceInfo, FAILURE_INTERNAL_ERROR);
+ mTestLooper.dispatchAll();
+ verify(mUnregistrationReceiver, times(1)).onError(0);
+ }
+
+ @Test
+ public void onOtDaemonDied_unregisterAll() {
+ prepareTest();
+
+ DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
+ DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
+
+ ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
+ ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService",
+ "_test._tcp",
+ List.of("_subtype1", "_subtype2"),
+ 12345,
+ List.of(txt1, txt2),
+ mRegistrationReceiver,
+ 16 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ verify(mMockNsdManager, times(1))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+ NsdManager.RegistrationListener actualListener1 =
+ actualRegistrationListenerCaptor.getValue();
+ actualListener1.onServiceRegistered(actualServiceInfoCaptor.getValue());
+
+ mNsdPublisher.registerService(
+ null,
+ "MyService2",
+ "_test._udp",
+ Collections.emptyList(),
+ 11111,
+ Collections.emptyList(),
+ mRegistrationReceiver,
+ 17 /* listenerId */);
+
+ mTestLooper.dispatchAll();
+
+ verify(mMockNsdManager, times(2))
+ .registerService(
+ actualServiceInfoCaptor.capture(),
+ eq(PROTOCOL_DNS_SD),
+ any(Executor.class),
+ actualRegistrationListenerCaptor.capture());
+ NsdManager.RegistrationListener actualListener2 =
+ actualRegistrationListenerCaptor.getAllValues().get(1);
+ actualListener2.onServiceRegistered(actualServiceInfoCaptor.getValue());
+
+ mNsdPublisher.onOtDaemonDied();
+ mTestLooper.dispatchAll();
+
+ verify(mMockNsdManager, times(1)).unregisterService(actualListener1);
+ verify(mMockNsdManager, times(1)).unregisterService(actualListener2);
+ }
+
+ private static DnsTxtAttribute makeTxtAttribute(String name, List<Integer> value) {
+ DnsTxtAttribute txtAttribute = new DnsTxtAttribute();
+
+ txtAttribute.name = name;
+ txtAttribute.value = new byte[value.size()];
+
+ for (int i = 0; i < value.size(); ++i) {
+ txtAttribute.value[i] = value.get(i).byteValue();
+ }
+
+ return txtAttribute;
+ }
+
+ // @Before and @Test run in different threads. NsdPublisher requires the jobs are run on the
+ // thread looper, so TestLooper needs to be created inside each test case to install the
+ // correct looper.
+ private void prepareTest() {
+ mTestLooper = new TestLooper();
+ Handler handler = new Handler(mTestLooper.getLooper());
+ mNsdPublisher = new NsdPublisher(mMockNsdManager, handler);
+ }
+}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 1d83abc..f626edf 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -87,6 +87,7 @@
@Mock private ParcelFileDescriptor mMockTunFd;
@Mock private InfraInterfaceController mMockInfraIfController;
@Mock private ThreadPersistentSettings mMockPersistentSettings;
+ @Mock private NsdPublisher mMockNsdPublisher;
private Context mContext;
private TestLooper mTestLooper;
private FakeOtDaemon mFakeOtDaemon;
@@ -117,12 +118,13 @@
mMockConnectivityManager,
mMockTunIfController,
mMockInfraIfController,
- mMockPersistentSettings);
+ mMockPersistentSettings,
+ mMockNsdPublisher);
mService.setTestNetworkAgent(mMockNetworkAgent);
}
@Test
- public void initialize_tunInterfaceSetToOtDaemon() throws Exception {
+ public void initialize_tunInterfaceAndNsdPublisherSetToOtDaemon() throws Exception {
when(mMockTunIfController.getTunFd()).thenReturn(mMockTunFd);
mService.initialize();
@@ -130,6 +132,7 @@
verify(mMockTunIfController, times(1)).createTunInterface();
assertThat(mFakeOtDaemon.getTunFd()).isEqualTo(mMockTunFd);
+ assertThat(mFakeOtDaemon.getNsdPublisher()).isEqualTo(mMockNsdPublisher);
}
@Test