Merge changes I298816ac,I3f41b4fe,Ibd782029 am: f71485f351

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/2238117

Change-Id: I9f9f16235a359df47aeaf8e51f6578d48a478115
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Paul Hu
2023-01-17 16:36:23 +00:00
committed by Automerger Merge Worker
3 changed files with 402 additions and 20 deletions

View File

@@ -242,6 +242,11 @@ public final class NsdManager {
/** @hide */
public static final int UNREGISTER_CLIENT = 22;
/** @hide */
public static final int MDNS_MONITORING_SOCKETS_CLEANUP = 23;
/** @hide */
public static final int MDNS_DISCOVERY_MANAGER_EVENT = 24;
/** Dns based service discovery protocol */
public static final int PROTOCOL_DNS_SD = 0x0001;

View File

@@ -17,9 +17,11 @@
package com.android.server;
import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT;
import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
@@ -45,6 +47,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -58,6 +61,9 @@ import com.android.net.module.util.PermissionUtils;
import com.android.server.connectivity.mdns.ExecutorProvider;
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
import com.android.server.connectivity.mdns.MdnsMultinetworkSocketClient;
import com.android.server.connectivity.mdns.MdnsSearchOptions;
import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
import com.android.server.connectivity.mdns.MdnsServiceInfo;
import com.android.server.connectivity.mdns.MdnsSocketClientBase;
import com.android.server.connectivity.mdns.MdnsSocketProvider;
@@ -68,6 +74,9 @@ import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Network Service Discovery Service handles remote service discovery operation requests by
@@ -79,6 +88,7 @@ public class NsdService extends INsdManager.Stub {
private static final String TAG = "NsdService";
private static final String MDNS_TAG = "mDnsConnector";
private static final String MDNS_DISCOVERY_MANAGER_VERSION = "mdns_discovery_manager_version";
private static final String LOCAL_DOMAIN_NAME = "local";
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final long CLEANUP_DELAY_MS = 10000;
@@ -94,10 +104,11 @@ public class NsdService extends INsdManager.Stub {
private final MdnsDiscoveryManager mMdnsDiscoveryManager;
@Nullable
private final MdnsSocketProvider mMdnsSocketProvider;
// WARNING : Accessing this value in any thread is not safe, it must only be changed in the
// WARNING : Accessing these values in any thread is not safe, it must only be changed in the
// state machine thread. If change this outside state machine, it will need to introduce
// synchronization.
private boolean mIsDaemonStarted = false;
private boolean mIsMonitoringSocketsStarted = false;
/**
* Clients receiving asynchronous messages
@@ -114,6 +125,95 @@ public class NsdService extends INsdManager.Stub {
// The count of the connected legacy clients.
private int mLegacyClientCount = 0;
private static class MdnsListener implements MdnsServiceBrowserListener {
protected final int mClientId;
protected final int mTransactionId;
@NonNull
protected final NsdServiceInfo mReqServiceInfo;
@NonNull
protected final String mListenedServiceType;
MdnsListener(int clientId, int transactionId, @NonNull NsdServiceInfo reqServiceInfo,
@NonNull String listenedServiceType) {
mClientId = clientId;
mTransactionId = transactionId;
mReqServiceInfo = reqServiceInfo;
mListenedServiceType = listenedServiceType;
}
@NonNull
public String getListenedServiceType() {
return mListenedServiceType;
}
@Override
public void onServiceFound(@NonNull MdnsServiceInfo serviceInfo) { }
@Override
public void onServiceUpdated(@NonNull MdnsServiceInfo serviceInfo) { }
@Override
public void onServiceRemoved(@NonNull MdnsServiceInfo serviceInfo) { }
@Override
public void onServiceNameDiscovered(@NonNull MdnsServiceInfo serviceInfo) { }
@Override
public void onServiceNameRemoved(@NonNull MdnsServiceInfo serviceInfo) { }
@Override
public void onSearchStoppedWithError(int error) { }
@Override
public void onSearchFailedToStart() { }
@Override
public void onDiscoveryQuerySent(@NonNull List<String> subtypes, int transactionId) { }
@Override
public void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode) { }
}
private class DiscoveryListener extends MdnsListener {
DiscoveryListener(int clientId, int transactionId, @NonNull NsdServiceInfo reqServiceInfo,
@NonNull String listenServiceType) {
super(clientId, transactionId, reqServiceInfo, listenServiceType);
}
@Override
public void onServiceNameDiscovered(@NonNull MdnsServiceInfo serviceInfo) {
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
NsdManager.SERVICE_FOUND,
new MdnsEvent(mClientId, mReqServiceInfo.getServiceType(), serviceInfo));
}
@Override
public void onServiceNameRemoved(@NonNull MdnsServiceInfo serviceInfo) {
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
NsdManager.SERVICE_LOST,
new MdnsEvent(mClientId, mReqServiceInfo.getServiceType(), serviceInfo));
}
}
/**
* Data class of mdns service callback information.
*/
private static class MdnsEvent {
final int mClientId;
@NonNull
final String mRequestedServiceType;
@NonNull
final MdnsServiceInfo mMdnsServiceInfo;
MdnsEvent(int clientId, @NonNull String requestedServiceType,
@NonNull MdnsServiceInfo mdnsServiceInfo) {
mClientId = clientId;
mRequestedServiceType = requestedServiceType;
mMdnsServiceInfo = mdnsServiceInfo;
}
}
private class NsdStateMachine extends StateMachine {
private final DefaultState mDefaultState = new DefaultState();
@@ -164,6 +264,31 @@ public class NsdService extends INsdManager.Stub {
this.removeMessages(NsdManager.DAEMON_CLEANUP);
}
private void maybeStartMonitoringSockets() {
if (mIsMonitoringSocketsStarted) {
if (DBG) Log.d(TAG, "Socket monitoring is already started.");
return;
}
mMdnsSocketProvider.startMonitoringSockets();
mIsMonitoringSocketsStarted = true;
}
private void maybeStopMonitoringSockets() {
if (!mIsMonitoringSocketsStarted) {
if (DBG) Log.d(TAG, "Socket monitoring has not been started.");
return;
}
mMdnsSocketProvider.stopMonitoringSockets();
mIsMonitoringSocketsStarted = false;
}
private void maybeStopMonitoringSocketsIfNoActiveRequest() {
if (!isAnyRequestActive()) {
maybeStopMonitoringSockets();
}
}
NsdStateMachine(String name, Handler handler) {
super(name, handler);
addState(mDefaultState);
@@ -195,11 +320,17 @@ public class NsdService extends INsdManager.Stub {
final NsdServiceConnector connector = (NsdServiceConnector) msg.obj;
cInfo = mClients.remove(connector);
if (cInfo != null) {
if (mMdnsDiscoveryManager != null) {
cInfo.unregisterAllListeners();
}
cInfo.expungeAllRequests();
if (cInfo.isLegacy()) {
mLegacyClientCount -= 1;
}
}
if (mMdnsDiscoveryManager != null) {
maybeStopMonitoringSocketsIfNoActiveRequest();
}
maybeScheduleStop();
break;
case NsdManager.DISCOVER_SERVICES:
@@ -251,6 +382,9 @@ public class NsdService extends INsdManager.Stub {
maybeStartDaemon();
}
break;
case NsdManager.MDNS_MONITORING_SOCKETS_CLEANUP:
maybeStopMonitoringSockets();
break;
default:
Log.e(TAG, "Unhandled " + msg);
return NOT_HANDLED;
@@ -300,6 +434,47 @@ public class NsdService extends INsdManager.Stub {
maybeScheduleStop();
}
private void storeListenerMap(int clientId, int transactionId, MdnsListener listener,
ClientInfo clientInfo) {
clientInfo.mClientIds.put(clientId, transactionId);
clientInfo.mListeners.put(clientId, listener);
mIdToClientInfoMap.put(transactionId, clientInfo);
removeMessages(NsdManager.MDNS_MONITORING_SOCKETS_CLEANUP);
}
private void removeListenerMap(int clientId, int transactionId, ClientInfo clientInfo) {
clientInfo.mClientIds.delete(clientId);
clientInfo.mListeners.delete(clientId);
mIdToClientInfoMap.remove(transactionId);
maybeStopMonitoringSocketsIfNoActiveRequest();
}
/**
* Check the given service type is valid and construct it to a service type
* which can use for discovery / resolution service.
*
* <p> The valid service type should be 2 labels, or 3 labels if the query is for a
* subtype (see RFC6763 7.1). Each label is up to 63 characters and must start with an
* underscore; they are alphanumerical characters or dashes or underscore, except the
* last one that is just alphanumerical. The last label must be _tcp or _udp.
*
* @param serviceType the request service type for discovery / resolution service
* @return constructed service type or null if the given service type is invalid.
*/
@Nullable
private String constructServiceType(String serviceType) {
if (TextUtils.isEmpty(serviceType)) return null;
final Pattern serviceTypePattern = Pattern.compile(
"^(_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]\\.)?"
+ "(_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]\\._(?:tcp|udp))$");
final Matcher matcher = serviceTypePattern.matcher(serviceType);
if (!matcher.matches()) return null;
return matcher.group(1) == null
? serviceType + ".local"
: matcher.group(1) + "._sub" + matcher.group(2) + ".local";
}
@Override
public boolean processMessage(Message msg) {
final ClientInfo clientInfo;
@@ -325,19 +500,40 @@ public class NsdService extends INsdManager.Stub {
break;
}
maybeStartDaemon();
final NsdServiceInfo info = args.serviceInfo;
id = getUniqueId();
if (discoverServices(id, args.serviceInfo)) {
if (DBG) {
Log.d(TAG, "Discover " + msg.arg2 + " " + id
+ args.serviceInfo.getServiceType());
if (mMdnsDiscoveryManager != null) {
final String serviceType = constructServiceType(info.getServiceType());
if (serviceType == null) {
clientInfo.onDiscoverServicesFailed(clientId,
NsdManager.FAILURE_INTERNAL_ERROR);
break;
}
storeRequestMap(clientId, id, clientInfo, msg.what);
clientInfo.onDiscoverServicesStarted(clientId, args.serviceInfo);
maybeStartMonitoringSockets();
final MdnsListener listener =
new DiscoveryListener(clientId, id, info, serviceType);
final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
.setNetwork(info.getNetwork())
.setIsPassiveMode(true)
.build();
mMdnsDiscoveryManager.registerListener(serviceType, listener, options);
storeListenerMap(clientId, id, listener, clientInfo);
clientInfo.onDiscoverServicesStarted(clientId, info);
} else {
stopServiceDiscovery(id);
clientInfo.onDiscoverServicesFailed(clientId,
NsdManager.FAILURE_INTERNAL_ERROR);
maybeStartDaemon();
if (discoverServices(id, info)) {
if (DBG) {
Log.d(TAG, "Discover " + msg.arg2 + " " + id
+ info.getServiceType());
}
storeRequestMap(clientId, id, clientInfo, msg.what);
clientInfo.onDiscoverServicesStarted(clientId, info);
} else {
stopServiceDiscovery(id);
clientInfo.onDiscoverServicesFailed(clientId,
NsdManager.FAILURE_INTERNAL_ERROR);
}
}
break;
case NsdManager.STOP_DISCOVERY:
@@ -359,12 +555,25 @@ public class NsdService extends INsdManager.Stub {
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
break;
}
removeRequestMap(clientId, id, clientInfo);
if (stopServiceDiscovery(id)) {
if (mMdnsDiscoveryManager != null) {
final MdnsListener listener = clientInfo.mListeners.get(clientId);
if (listener == null) {
clientInfo.onStopDiscoveryFailed(
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
break;
}
mMdnsDiscoveryManager.unregisterListener(
listener.getListenedServiceType(), listener);
removeListenerMap(clientId, id, clientInfo);
clientInfo.onStopDiscoverySucceeded(clientId);
} else {
clientInfo.onStopDiscoveryFailed(
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
removeRequestMap(clientId, id, clientInfo);
if (stopServiceDiscovery(id)) {
clientInfo.onStopDiscoverySucceeded(clientId);
} else {
clientInfo.onStopDiscoveryFailed(
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
}
}
break;
case NsdManager.REGISTER_SERVICE:
@@ -450,6 +659,11 @@ public class NsdService extends INsdManager.Stub {
return NOT_HANDLED;
}
break;
case MDNS_DISCOVERY_MANAGER_EVENT:
if (!handleMdnsDiscoveryManagerEvent(msg.arg1, msg.arg2, msg.obj)) {
return NOT_HANDLED;
}
break;
default:
return NOT_HANDLED;
}
@@ -612,6 +826,48 @@ public class NsdService extends INsdManager.Stub {
}
return true;
}
private NsdServiceInfo buildNsdServiceInfoFromMdnsEvent(final MdnsEvent event) {
final MdnsServiceInfo serviceInfo = event.mMdnsServiceInfo;
final String serviceType = event.mRequestedServiceType;
final String serviceName = serviceInfo.getServiceInstanceName();
final NsdServiceInfo servInfo = new NsdServiceInfo(serviceName, serviceType);
final Network network = serviceInfo.getNetwork();
setServiceNetworkForCallback(
servInfo,
network == null ? NETID_UNSET : network.netId,
serviceInfo.getInterfaceIndex());
return servInfo;
}
private boolean handleMdnsDiscoveryManagerEvent(
int transactionId, int code, Object obj) {
final ClientInfo clientInfo = mIdToClientInfoMap.get(transactionId);
if (clientInfo == null) {
Log.e(TAG, String.format(
"id %d for %d has no client mapping", transactionId, code));
return false;
}
final MdnsEvent event = (MdnsEvent) obj;
final int clientId = event.mClientId;
final NsdServiceInfo info = buildNsdServiceInfoFromMdnsEvent(event);
if (DBG) {
Log.d(TAG, String.format("MdnsDiscoveryManager event code=%s transactionId=%d",
NsdManager.nameOf(code), transactionId));
}
switch (code) {
case NsdManager.SERVICE_FOUND:
clientInfo.onServiceFound(clientId, info);
break;
case NsdManager.SERVICE_LOST:
clientInfo.onServiceLost(clientId, info);
break;
default:
return false;
}
return true;
}
}
}
@@ -982,6 +1238,9 @@ public class NsdService extends INsdManager.Stub {
/* A map from client id to the type of the request we had received */
private final SparseIntArray mClientRequests = new SparseIntArray();
/* A map from client id to the MdnsListener */
private final SparseArray<MdnsListener> mListeners = new SparseArray<>();
// The target SDK of this client < Build.VERSION_CODES.S
private boolean mIsLegacy = false;
@@ -1043,6 +1302,15 @@ public class NsdService extends INsdManager.Stub {
mClientRequests.clear();
}
void unregisterAllListeners() {
for (int i = 0; i < mListeners.size(); i++) {
final MdnsListener listener = mListeners.valueAt(i);
mMdnsDiscoveryManager.unregisterListener(
listener.getListenedServiceType(), listener);
}
mListeners.clear();
}
// mClientIds is a sparse array of listener id -> mDnsClient id. For a given mDnsClient id,
// return the corresponding listener id. mDnsClient id is also called a global id.
private int getClientId(final int globalId) {

View File

@@ -28,6 +28,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
@@ -45,6 +46,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.net.INetd;
import android.net.InetAddresses;
import android.net.Network;
import android.net.mdns.aidl.DiscoveryInfo;
import android.net.mdns.aidl.GetAddressInfo;
import android.net.mdns.aidl.IMDnsEventListener;
@@ -70,6 +72,10 @@ import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.server.NsdService.Dependencies;
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
import com.android.server.connectivity.mdns.MdnsServiceInfo;
import com.android.server.connectivity.mdns.MdnsSocketProvider;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
@@ -86,6 +92,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
// TODOs:
@@ -99,7 +106,7 @@ public class NsdServiceTest {
private static final long CLEANUP_DELAY_MS = 500;
private static final long TIMEOUT_MS = 500;
private static final String SERVICE_NAME = "a_name";
private static final String SERVICE_TYPE = "a_type";
private static final String SERVICE_TYPE = "_test._tcp";
private static final String SERVICE_FULL_NAME = SERVICE_NAME + "." + SERVICE_TYPE;
private static final String DOMAIN_NAME = "mytestdevice.local";
private static final int PORT = 2201;
@@ -116,6 +123,8 @@ public class NsdServiceTest {
@Mock ContentResolver mResolver;
@Mock MDnsManager mMockMDnsM;
@Mock Dependencies mDeps;
@Mock MdnsDiscoveryManager mDiscoveryManager;
@Mock MdnsSocketProvider mSocketProvider;
HandlerThread mThread;
TestHandler mHandler;
NsdService mService;
@@ -558,6 +567,16 @@ public class NsdServiceTest {
anyInt()/* interfaceIdx */);
}
private void makeServiceWithMdnsDiscoveryManagerEnabled() {
doReturn(true).when(mDeps).isMdnsDiscoveryManagerEnabled(any(Context.class));
doReturn(mDiscoveryManager).when(mDeps).makeMdnsDiscoveryManager(any(), any());
doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any());
mService = makeService();
verify(mDeps).makeMdnsDiscoveryManager(any(), any());
verify(mDeps).makeMdnsSocketProvider(any(), any());
}
@Test
public void testMdnsDiscoveryManagerFeature() {
// Create NsdService w/o feature enabled.
@@ -566,12 +585,102 @@ public class NsdServiceTest {
verify(mDeps, never()).makeMdnsSocketProvider(any(), any());
// Create NsdService again w/ feature enabled.
doReturn(true).when(mDeps).isMdnsDiscoveryManagerEnabled(any(Context.class));
makeService();
verify(mDeps).makeMdnsDiscoveryManager(any(), any());
verify(mDeps).makeMdnsSocketProvider(any(), any());
makeServiceWithMdnsDiscoveryManagerEnabled();
}
@Test
public void testDiscoveryWithMdnsDiscoveryManager() {
makeServiceWithMdnsDiscoveryManagerEnabled();
final NsdManager client = connectClient(mService);
final DiscoveryListener discListener = mock(DiscoveryListener.class);
final Network network = new Network(999);
final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
// Verify the discovery start / stop.
final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
client.discoverServices(SERVICE_TYPE, PROTOCOL, network, r -> r.run(), discListener);
waitForIdle();
verify(mSocketProvider).startMonitoringSockets();
verify(mDiscoveryManager).registerListener(eq(serviceTypeWithLocalDomain),
listenerCaptor.capture(), argThat(options -> network.equals(options.getNetwork())));
verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(SERVICE_TYPE);
final MdnsServiceBrowserListener listener = listenerCaptor.getValue();
final MdnsServiceInfo foundInfo = new MdnsServiceInfo(
SERVICE_NAME, /* serviceInstanceName */
serviceTypeWithLocalDomain.split("\\."), /* serviceType */
List.of(), /* subtypes */
new String[] {"android", "local"}, /* hostName */
12345, /* port */
"192.0.2.0", /* ipv4Address */
"2001:db8::", /* ipv6Address */
List.of(), /* textStrings */
List.of(), /* textEntries */
1234, /* interfaceIndex */
network);
// Verify onServiceNameDiscovered callback
listener.onServiceNameDiscovered(foundInfo);
verify(discListener, timeout(TIMEOUT_MS)).onServiceFound(argThat(info ->
info.getServiceName().equals(SERVICE_NAME)
&& info.getServiceType().equals(SERVICE_TYPE)
&& info.getNetwork().equals(network)));
final MdnsServiceInfo removedInfo = new MdnsServiceInfo(
SERVICE_NAME, /* serviceInstanceName */
serviceTypeWithLocalDomain.split("\\."), /* serviceType */
null, /* subtypes */
null, /* hostName */
0, /* port */
null, /* ipv4Address */
null, /* ipv6Address */
null, /* textStrings */
null, /* textEntries */
1234, /* interfaceIndex */
network);
// Verify onServiceNameRemoved callback
listener.onServiceNameRemoved(removedInfo);
verify(discListener, timeout(TIMEOUT_MS)).onServiceLost(argThat(info ->
info.getServiceName().equals(SERVICE_NAME)
&& info.getServiceType().equals(SERVICE_TYPE)
&& info.getNetwork().equals(network)));
client.stopServiceDiscovery(discListener);
waitForIdle();
verify(mDiscoveryManager).unregisterListener(eq(serviceTypeWithLocalDomain), any());
verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStopped(SERVICE_TYPE);
verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).stopMonitoringSockets();
}
@Test
public void testDiscoveryWithMdnsDiscoveryManager_FailedWithInvalidServiceType() {
makeServiceWithMdnsDiscoveryManagerEnabled();
final NsdManager client = connectClient(mService);
final DiscoveryListener discListener = mock(DiscoveryListener.class);
final Network network = new Network(999);
final String invalidServiceType = "a_service";
client.discoverServices(
invalidServiceType, PROTOCOL, network, r -> r.run(), discListener);
waitForIdle();
verify(discListener, timeout(TIMEOUT_MS))
.onStartDiscoveryFailed(invalidServiceType, FAILURE_INTERNAL_ERROR);
final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
client.discoverServices(
serviceTypeWithLocalDomain, PROTOCOL, network, r -> r.run(), discListener);
waitForIdle();
verify(discListener, timeout(TIMEOUT_MS))
.onStartDiscoveryFailed(serviceTypeWithLocalDomain, FAILURE_INTERNAL_ERROR);
final String serviceTypeWithoutTcpOrUdpEnding = "_test._com";
client.discoverServices(
serviceTypeWithoutTcpOrUdpEnding, PROTOCOL, network, r -> r.run(), discListener);
waitForIdle();
verify(discListener, timeout(TIMEOUT_MS))
.onStartDiscoveryFailed(serviceTypeWithoutTcpOrUdpEnding, FAILURE_INTERNAL_ERROR);
}
private void waitForIdle() {
HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);