Merge "Remove synchronized lock in MdnsServiceTypeClient" into main
This commit is contained in:
@@ -17,7 +17,6 @@
|
|||||||
package com.android.server.connectivity.mdns;
|
package com.android.server.connectivity.mdns;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@@ -106,12 +105,11 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
|
|||||||
// Incompatible return type for override of Callable#call().
|
// Incompatible return type for override of Callable#call().
|
||||||
@SuppressWarnings("nullness:override.return.invalid")
|
@SuppressWarnings("nullness:override.return.invalid")
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
|
||||||
public Pair<Integer, List<String>> call() {
|
public Pair<Integer, List<String>> call() {
|
||||||
try {
|
try {
|
||||||
MdnsSocketClientBase requestSender = weakRequestSender.get();
|
MdnsSocketClientBase requestSender = weakRequestSender.get();
|
||||||
if (requestSender == null) {
|
if (requestSender == null) {
|
||||||
return null;
|
return Pair.create(-1, new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
int numQuestions = 0;
|
int numQuestions = 0;
|
||||||
@@ -158,7 +156,7 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
|
|||||||
|
|
||||||
if (numQuestions == 0) {
|
if (numQuestions == 0) {
|
||||||
// No query to send
|
// No query to send
|
||||||
return null;
|
return Pair.create(-1, new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header.
|
// Header.
|
||||||
@@ -197,7 +195,7 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOGGER.e(String.format("Failed to create mDNS packet for subtype: %s.",
|
LOGGER.e(String.format("Failed to create mDNS packet for subtype: %s.",
|
||||||
TextUtils.join(",", subtypes)), e);
|
TextUtils.join(",", subtypes)), e);
|
||||||
return null;
|
return Pair.create(-1, new ArrayList<>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ public class MdnsServiceTypeClient {
|
|||||||
private static final int DEFAULT_MTU = 1500;
|
private static final int DEFAULT_MTU = 1500;
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final int EVENT_START_QUERYTASK = 1;
|
static final int EVENT_START_QUERYTASK = 1;
|
||||||
|
static final int EVENT_QUERY_RESULT = 2;
|
||||||
|
|
||||||
private final String serviceType;
|
private final String serviceType;
|
||||||
private final String[] serviceTypeLabels;
|
private final String[] serviceTypeLabels;
|
||||||
@@ -66,11 +67,9 @@ public class MdnsServiceTypeClient {
|
|||||||
@NonNull private final SharedLog sharedLog;
|
@NonNull private final SharedLog sharedLog;
|
||||||
@NonNull private final Handler handler;
|
@NonNull private final Handler handler;
|
||||||
@NonNull private final Dependencies dependencies;
|
@NonNull private final Dependencies dependencies;
|
||||||
private final Object lock = new Object();
|
|
||||||
private final ArrayMap<MdnsServiceBrowserListener, MdnsSearchOptions> listeners =
|
private final ArrayMap<MdnsServiceBrowserListener, MdnsSearchOptions> listeners =
|
||||||
new ArrayMap<>();
|
new ArrayMap<>();
|
||||||
// TODO: change instanceNameToResponse to TreeMap with case insensitive comparator.
|
// TODO: change instanceNameToResponse to TreeMap with case insensitive comparator.
|
||||||
@GuardedBy("lock")
|
|
||||||
private final Map<String, MdnsResponse> instanceNameToResponse = new HashMap<>();
|
private final Map<String, MdnsResponse> instanceNameToResponse = new HashMap<>();
|
||||||
private final boolean removeServiceAfterTtlExpires =
|
private final boolean removeServiceAfterTtlExpires =
|
||||||
MdnsConfigs.removeServiceAfterTtlExpires();
|
MdnsConfigs.removeServiceAfterTtlExpires();
|
||||||
@@ -83,11 +82,8 @@ public class MdnsServiceTypeClient {
|
|||||||
// new subtypes. It stays the same between packets for same subtypes.
|
// new subtypes. It stays the same between packets for same subtypes.
|
||||||
private long currentSessionId = 0;
|
private long currentSessionId = 0;
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private QueryTask lastScheduledTask;
|
private ScheduledQueryTaskArgs lastScheduledQueryTaskArgs;
|
||||||
|
|
||||||
@GuardedBy("lock")
|
|
||||||
private long lastSentTime;
|
private long lastSentTime;
|
||||||
|
|
||||||
private class QueryTaskHandler extends Handler {
|
private class QueryTaskHandler extends Handler {
|
||||||
@@ -98,9 +94,48 @@ public class MdnsServiceTypeClient {
|
|||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message msg) {
|
public void handleMessage(Message msg) {
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case EVENT_START_QUERYTASK:
|
case EVENT_START_QUERYTASK: {
|
||||||
handleStartQueryTask((QueryTask) msg.obj);
|
final ScheduledQueryTaskArgs taskArgs = (ScheduledQueryTaskArgs) msg.obj;
|
||||||
|
// QueryTask should be run immediately after being created (not be scheduled in
|
||||||
|
// advance). Because the result of "makeResponsesForResolve" depends on answers
|
||||||
|
// that were received before it is called, so to take into account all answers
|
||||||
|
// before sending the query, it needs to be called just before sending it.
|
||||||
|
final List<MdnsResponse> servicesToResolve = makeResponsesForResolve(socketKey);
|
||||||
|
final QueryTask queryTask = new QueryTask(taskArgs, servicesToResolve,
|
||||||
|
servicesToResolve.size() < listeners.size() /* sendDiscoveryQueries */);
|
||||||
|
executor.submit(queryTask);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case EVENT_QUERY_RESULT: {
|
||||||
|
final QuerySentResult sentResult = (QuerySentResult) msg.obj;
|
||||||
|
if (MdnsConfigs.useSessionIdToScheduleMdnsTask()) {
|
||||||
|
// In case that the task is not canceled successfully, use session ID to
|
||||||
|
// check if this task should continue to schedule more.
|
||||||
|
if (sentResult.taskArgs.sessionId != currentSessionId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((sentResult.transactionId != -1)) {
|
||||||
|
for (int i = 0; i < listeners.size(); i++) {
|
||||||
|
listeners.keyAt(i).onDiscoveryQuerySent(
|
||||||
|
sentResult.subTypes, sentResult.transactionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tryRemoveServiceAfterTtlExpires();
|
||||||
|
|
||||||
|
final QueryTaskConfig nextRunConfig =
|
||||||
|
sentResult.taskArgs.config.getConfigForNextRun();
|
||||||
|
final long now = clock.elapsedRealtime();
|
||||||
|
lastSentTime = now;
|
||||||
|
final long minRemainingTtl = getMinRemainingTtl(now);
|
||||||
|
final long timeToRun = calculateTimeToRun(lastScheduledQueryTaskArgs,
|
||||||
|
nextRunConfig, now, minRemainingTtl, lastSentTime);
|
||||||
|
scheduleNextRun(nextRunConfig, minRemainingTtl, now, timeToRun,
|
||||||
|
lastScheduledQueryTaskArgs.sessionId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
sharedLog.e("Unrecognized event " + msg.what);
|
sharedLog.e("Unrecognized event " + msg.what);
|
||||||
break;
|
break;
|
||||||
@@ -134,6 +169,13 @@ public class MdnsServiceTypeClient {
|
|||||||
public boolean hasMessages(@NonNull Handler handler, int what) {
|
public boolean hasMessages(@NonNull Handler handler, int what) {
|
||||||
return handler.hasMessages(what);
|
return handler.hasMessages(what);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Handler#post(Runnable)
|
||||||
|
*/
|
||||||
|
public void sendMessage(@NonNull Handler handler, @NonNull Message message) {
|
||||||
|
handler.sendMessage(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -236,62 +278,57 @@ public class MdnsServiceTypeClient {
|
|||||||
@NonNull MdnsServiceBrowserListener listener,
|
@NonNull MdnsServiceBrowserListener listener,
|
||||||
@NonNull MdnsSearchOptions searchOptions) {
|
@NonNull MdnsSearchOptions searchOptions) {
|
||||||
ensureRunningOnHandlerThread(handler);
|
ensureRunningOnHandlerThread(handler);
|
||||||
synchronized (lock) {
|
this.searchOptions = searchOptions;
|
||||||
this.searchOptions = searchOptions;
|
boolean hadReply = false;
|
||||||
boolean hadReply = false;
|
if (listeners.put(listener, searchOptions) == null) {
|
||||||
if (listeners.put(listener, searchOptions) == null) {
|
for (MdnsResponse existingResponse : instanceNameToResponse.values()) {
|
||||||
for (MdnsResponse existingResponse : instanceNameToResponse.values()) {
|
if (!responseMatchesOptions(existingResponse, searchOptions)) continue;
|
||||||
if (!responseMatchesOptions(existingResponse, searchOptions)) continue;
|
final MdnsServiceInfo info =
|
||||||
final MdnsServiceInfo info =
|
buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
|
||||||
buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
|
listener.onServiceNameDiscovered(info);
|
||||||
listener.onServiceNameDiscovered(info);
|
if (existingResponse.isComplete()) {
|
||||||
if (existingResponse.isComplete()) {
|
listener.onServiceFound(info);
|
||||||
listener.onServiceFound(info);
|
hadReply = true;
|
||||||
hadReply = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remove the next scheduled periodical task.
|
}
|
||||||
removeScheduledTaskLock();
|
// Remove the next scheduled periodical task.
|
||||||
// Keep tracking the ScheduledFuture for the task so we can cancel it if caller is not
|
removeScheduledTask();
|
||||||
// interested anymore.
|
// Keep tracking the ScheduledFuture for the task so we can cancel it if caller is not
|
||||||
final QueryTaskConfig taskConfig = new QueryTaskConfig(
|
// interested anymore.
|
||||||
searchOptions.getSubtypes(),
|
final QueryTaskConfig taskConfig = new QueryTaskConfig(
|
||||||
searchOptions.isPassiveMode(),
|
searchOptions.getSubtypes(),
|
||||||
searchOptions.onlyUseIpv6OnIpv6OnlyNetworks(),
|
searchOptions.isPassiveMode(),
|
||||||
searchOptions.numOfQueriesBeforeBackoff(),
|
searchOptions.onlyUseIpv6OnIpv6OnlyNetworks(),
|
||||||
socketKey);
|
searchOptions.numOfQueriesBeforeBackoff(),
|
||||||
final long now = clock.elapsedRealtime();
|
socketKey);
|
||||||
if (lastSentTime == 0) {
|
final long now = clock.elapsedRealtime();
|
||||||
lastSentTime = now;
|
if (lastSentTime == 0) {
|
||||||
}
|
lastSentTime = now;
|
||||||
if (hadReply) {
|
}
|
||||||
final QueryTaskConfig queryTaskConfig = taskConfig.getConfigForNextRun();
|
if (hadReply) {
|
||||||
final long minRemainingTtl = getMinRemainingTtlLocked(now);
|
final QueryTaskConfig queryTaskConfig = taskConfig.getConfigForNextRun();
|
||||||
final long timeToRun = now + queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
|
final long minRemainingTtl = getMinRemainingTtl(now);
|
||||||
scheduleNextRunLocked(
|
final long timeToRun = now + queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
|
||||||
queryTaskConfig, minRemainingTtl, now, timeToRun, currentSessionId);
|
scheduleNextRun(
|
||||||
} else {
|
queryTaskConfig, minRemainingTtl, now, timeToRun, currentSessionId);
|
||||||
lastScheduledTask = new QueryTask(taskConfig,
|
} else {
|
||||||
now /* timeToRun */,
|
final List<MdnsResponse> servicesToResolve = makeResponsesForResolve(socketKey);
|
||||||
now + getMinRemainingTtlLocked(now)/* minTtlExpirationTimeWhenScheduled */,
|
lastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(taskConfig, now /* timeToRun */,
|
||||||
currentSessionId);
|
now + getMinRemainingTtl(now)/* minTtlExpirationTimeWhenScheduled */,
|
||||||
handleStartQueryTask(lastScheduledTask);
|
currentSessionId);
|
||||||
}
|
final QueryTask queryTask = new QueryTask(lastScheduledQueryTaskArgs, servicesToResolve,
|
||||||
|
servicesToResolve.size() < listeners.size() /* sendDiscoveryQueries */);
|
||||||
|
executor.submit(queryTask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("lock")
|
private void removeScheduledTask() {
|
||||||
private void removeScheduledTaskLock() {
|
|
||||||
dependencies.removeMessages(handler, EVENT_START_QUERYTASK);
|
dependencies.removeMessages(handler, EVENT_START_QUERYTASK);
|
||||||
sharedLog.log("Remove EVENT_START_QUERYTASK"
|
sharedLog.log("Remove EVENT_START_QUERYTASK"
|
||||||
+ ", current session: " + currentSessionId);
|
+ ", current session: " + currentSessionId);
|
||||||
++currentSessionId;
|
++currentSessionId;
|
||||||
lastScheduledTask = null;
|
lastScheduledQueryTaskArgs = null;
|
||||||
}
|
|
||||||
|
|
||||||
private void handleStartQueryTask(@NonNull QueryTask task) {
|
|
||||||
executor.submit(task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean responseMatchesOptions(@NonNull MdnsResponse response,
|
private boolean responseMatchesOptions(@NonNull MdnsResponse response,
|
||||||
@@ -323,15 +360,13 @@ public class MdnsServiceTypeClient {
|
|||||||
*/
|
*/
|
||||||
public boolean stopSendAndReceive(@NonNull MdnsServiceBrowserListener listener) {
|
public boolean stopSendAndReceive(@NonNull MdnsServiceBrowserListener listener) {
|
||||||
ensureRunningOnHandlerThread(handler);
|
ensureRunningOnHandlerThread(handler);
|
||||||
synchronized (lock) {
|
if (listeners.remove(listener) == null) {
|
||||||
if (listeners.remove(listener) == null) {
|
|
||||||
return listeners.isEmpty();
|
|
||||||
}
|
|
||||||
if (listeners.isEmpty()) {
|
|
||||||
removeScheduledTaskLock();
|
|
||||||
}
|
|
||||||
return listeners.isEmpty();
|
return listeners.isEmpty();
|
||||||
}
|
}
|
||||||
|
if (listeners.isEmpty()) {
|
||||||
|
removeScheduledTask();
|
||||||
|
}
|
||||||
|
return listeners.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -340,51 +375,48 @@ public class MdnsServiceTypeClient {
|
|||||||
public synchronized void processResponse(@NonNull MdnsPacket packet,
|
public synchronized void processResponse(@NonNull MdnsPacket packet,
|
||||||
@NonNull SocketKey socketKey) {
|
@NonNull SocketKey socketKey) {
|
||||||
ensureRunningOnHandlerThread(handler);
|
ensureRunningOnHandlerThread(handler);
|
||||||
synchronized (lock) {
|
// Augment the list of current known responses, and generated responses for resolve
|
||||||
// Augment the list of current known responses, and generated responses for resolve
|
// requests if there is no known response
|
||||||
// requests if there is no known response
|
final List<MdnsResponse> currentList = new ArrayList<>(instanceNameToResponse.values());
|
||||||
final List<MdnsResponse> currentList = new ArrayList<>(instanceNameToResponse.values());
|
List<MdnsResponse> additionalResponses = makeResponsesForResolve(socketKey);
|
||||||
List<MdnsResponse> additionalResponses = makeResponsesForResolve(socketKey);
|
for (MdnsResponse additionalResponse : additionalResponses) {
|
||||||
for (MdnsResponse additionalResponse : additionalResponses) {
|
if (!instanceNameToResponse.containsKey(
|
||||||
if (!instanceNameToResponse.containsKey(
|
additionalResponse.getServiceInstanceName())) {
|
||||||
additionalResponse.getServiceInstanceName())) {
|
currentList.add(additionalResponse);
|
||||||
currentList.add(additionalResponse);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
final Pair<ArraySet<MdnsResponse>, ArrayList<MdnsResponse>> augmentedResult =
|
}
|
||||||
responseDecoder.augmentResponses(packet, currentList,
|
final Pair<ArraySet<MdnsResponse>, ArrayList<MdnsResponse>> augmentedResult =
|
||||||
socketKey.getInterfaceIndex(), socketKey.getNetwork());
|
responseDecoder.augmentResponses(packet, currentList,
|
||||||
|
socketKey.getInterfaceIndex(), socketKey.getNetwork());
|
||||||
|
|
||||||
final ArraySet<MdnsResponse> modifiedResponse = augmentedResult.first;
|
final ArraySet<MdnsResponse> modifiedResponse = augmentedResult.first;
|
||||||
final ArrayList<MdnsResponse> allResponses = augmentedResult.second;
|
final ArrayList<MdnsResponse> allResponses = augmentedResult.second;
|
||||||
|
|
||||||
for (MdnsResponse response : allResponses) {
|
for (MdnsResponse response : allResponses) {
|
||||||
if (modifiedResponse.contains(response)) {
|
if (modifiedResponse.contains(response)) {
|
||||||
if (response.isGoodbye()) {
|
if (response.isGoodbye()) {
|
||||||
onGoodbyeReceivedLocked(response.getServiceInstanceName());
|
onGoodbyeReceived(response.getServiceInstanceName());
|
||||||
} else {
|
} else {
|
||||||
onResponseModifiedLocked(response);
|
onResponseModified(response);
|
||||||
}
|
|
||||||
} else if (instanceNameToResponse.containsKey(response.getServiceInstanceName())) {
|
|
||||||
// If the response is not modified and already in the cache. The cache will
|
|
||||||
// need to be updated to refresh the last receipt time.
|
|
||||||
instanceNameToResponse.put(response.getServiceInstanceName(), response);
|
|
||||||
}
|
}
|
||||||
|
} else if (instanceNameToResponse.containsKey(response.getServiceInstanceName())) {
|
||||||
|
// If the response is not modified and already in the cache. The cache will
|
||||||
|
// need to be updated to refresh the last receipt time.
|
||||||
|
instanceNameToResponse.put(response.getServiceInstanceName(), response);
|
||||||
}
|
}
|
||||||
if (dependencies.hasMessages(handler, EVENT_START_QUERYTASK)
|
}
|
||||||
&& lastScheduledTask != null
|
if (dependencies.hasMessages(handler, EVENT_START_QUERYTASK)
|
||||||
&& lastScheduledTask.config.shouldUseQueryBackoff()) {
|
&& lastScheduledQueryTaskArgs != null
|
||||||
final long now = clock.elapsedRealtime();
|
&& lastScheduledQueryTaskArgs.config.shouldUseQueryBackoff()) {
|
||||||
final long minRemainingTtl = getMinRemainingTtlLocked(now);
|
final long now = clock.elapsedRealtime();
|
||||||
final long timeToRun = calculateTimeToRun(lastScheduledTask,
|
final long minRemainingTtl = getMinRemainingTtl(now);
|
||||||
lastScheduledTask.config, now,
|
final long timeToRun = calculateTimeToRun(lastScheduledQueryTaskArgs,
|
||||||
minRemainingTtl, lastSentTime);
|
lastScheduledQueryTaskArgs.config, now,
|
||||||
if (timeToRun > lastScheduledTask.timeToRun) {
|
minRemainingTtl, lastSentTime);
|
||||||
QueryTaskConfig lastTaskConfig = lastScheduledTask.config;
|
if (timeToRun > lastScheduledQueryTaskArgs.timeToRun) {
|
||||||
removeScheduledTaskLock();
|
QueryTaskConfig lastTaskConfig = lastScheduledQueryTaskArgs.config;
|
||||||
scheduleNextRunLocked(
|
removeScheduledTask();
|
||||||
lastTaskConfig, minRemainingTtl, now, timeToRun, currentSessionId);
|
scheduleNextRun(lastTaskConfig, minRemainingTtl, now, timeToRun, currentSessionId);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,29 +431,26 @@ public class MdnsServiceTypeClient {
|
|||||||
/** Notify all services are removed because the socket is destroyed. */
|
/** Notify all services are removed because the socket is destroyed. */
|
||||||
public void notifySocketDestroyed() {
|
public void notifySocketDestroyed() {
|
||||||
ensureRunningOnHandlerThread(handler);
|
ensureRunningOnHandlerThread(handler);
|
||||||
synchronized (lock) {
|
for (MdnsResponse response : instanceNameToResponse.values()) {
|
||||||
for (MdnsResponse response : instanceNameToResponse.values()) {
|
final String name = response.getServiceInstanceName();
|
||||||
final String name = response.getServiceInstanceName();
|
if (name == null) continue;
|
||||||
if (name == null) continue;
|
for (int i = 0; i < listeners.size(); i++) {
|
||||||
for (int i = 0; i < listeners.size(); i++) {
|
if (!responseMatchesOptions(response, listeners.valueAt(i))) continue;
|
||||||
if (!responseMatchesOptions(response, listeners.valueAt(i))) continue;
|
final MdnsServiceBrowserListener listener = listeners.keyAt(i);
|
||||||
final MdnsServiceBrowserListener listener = listeners.keyAt(i);
|
final MdnsServiceInfo serviceInfo =
|
||||||
final MdnsServiceInfo serviceInfo =
|
buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
|
||||||
buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
|
if (response.isComplete()) {
|
||||||
if (response.isComplete()) {
|
sharedLog.log("Socket destroyed. onServiceRemoved: " + name);
|
||||||
sharedLog.log("Socket destroyed. onServiceRemoved: " + name);
|
listener.onServiceRemoved(serviceInfo);
|
||||||
listener.onServiceRemoved(serviceInfo);
|
|
||||||
}
|
|
||||||
sharedLog.log("Socket destroyed. onServiceNameRemoved: " + name);
|
|
||||||
listener.onServiceNameRemoved(serviceInfo);
|
|
||||||
}
|
}
|
||||||
|
sharedLog.log("Socket destroyed. onServiceNameRemoved: " + name);
|
||||||
|
listener.onServiceNameRemoved(serviceInfo);
|
||||||
}
|
}
|
||||||
removeScheduledTaskLock();
|
|
||||||
}
|
}
|
||||||
|
removeScheduledTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("lock")
|
private void onResponseModified(@NonNull MdnsResponse response) {
|
||||||
private void onResponseModifiedLocked(@NonNull MdnsResponse response) {
|
|
||||||
final String serviceInstanceName = response.getServiceInstanceName();
|
final String serviceInstanceName = response.getServiceInstanceName();
|
||||||
final MdnsResponse currentResponse =
|
final MdnsResponse currentResponse =
|
||||||
instanceNameToResponse.get(serviceInstanceName);
|
instanceNameToResponse.get(serviceInstanceName);
|
||||||
@@ -467,8 +496,7 @@ public class MdnsServiceTypeClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("lock")
|
private void onGoodbyeReceived(@Nullable String serviceInstanceName) {
|
||||||
private void onGoodbyeReceivedLocked(@Nullable String serviceInstanceName) {
|
|
||||||
final MdnsResponse response = instanceNameToResponse.remove(serviceInstanceName);
|
final MdnsResponse response = instanceNameToResponse.remove(serviceInstanceName);
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
return;
|
return;
|
||||||
@@ -660,34 +688,80 @@ public class MdnsServiceTypeClient {
|
|||||||
return resolveResponses;
|
return resolveResponses;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A FutureTask that enqueues a single query, and schedule a new FutureTask for the next task.
|
private void tryRemoveServiceAfterTtlExpires() {
|
||||||
private class QueryTask implements Runnable {
|
if (!shouldRemoveServiceAfterTtlExpires()) return;
|
||||||
|
|
||||||
|
Iterator<MdnsResponse> iter = instanceNameToResponse.values().iterator();
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
MdnsResponse existingResponse = iter.next();
|
||||||
|
if (existingResponse.hasServiceRecord()
|
||||||
|
&& existingResponse.getServiceRecord()
|
||||||
|
.getRemainingTTL(clock.elapsedRealtime()) == 0) {
|
||||||
|
iter.remove();
|
||||||
|
for (int i = 0; i < listeners.size(); i++) {
|
||||||
|
if (!responseMatchesOptions(existingResponse, listeners.valueAt(i))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final MdnsServiceBrowserListener listener = listeners.keyAt(i);
|
||||||
|
if (existingResponse.getServiceInstanceName() != null) {
|
||||||
|
final MdnsServiceInfo serviceInfo = buildMdnsServiceInfoFromResponse(
|
||||||
|
existingResponse, serviceTypeLabels);
|
||||||
|
if (existingResponse.isComplete()) {
|
||||||
|
sharedLog.log("TTL expired. onServiceRemoved: " + serviceInfo);
|
||||||
|
listener.onServiceRemoved(serviceInfo);
|
||||||
|
}
|
||||||
|
sharedLog.log("TTL expired. onServiceNameRemoved: " + serviceInfo);
|
||||||
|
listener.onServiceNameRemoved(serviceInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ScheduledQueryTaskArgs {
|
||||||
private final QueryTaskConfig config;
|
private final QueryTaskConfig config;
|
||||||
private final long timeToRun;
|
private final long timeToRun;
|
||||||
private final long minTtlExpirationTimeWhenScheduled;
|
private final long minTtlExpirationTimeWhenScheduled;
|
||||||
private final long sessionId;
|
private final long sessionId;
|
||||||
|
|
||||||
QueryTask(@NonNull QueryTaskConfig config, long timeToRun,
|
ScheduledQueryTaskArgs(@NonNull QueryTaskConfig config, long timeToRun,
|
||||||
long minTtlExpirationTimeWhenScheduled,
|
long minTtlExpirationTimeWhenScheduled, long sessionId) {
|
||||||
long sessionId) {
|
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.timeToRun = timeToRun;
|
this.timeToRun = timeToRun;
|
||||||
this.minTtlExpirationTimeWhenScheduled = minTtlExpirationTimeWhenScheduled;
|
this.minTtlExpirationTimeWhenScheduled = minTtlExpirationTimeWhenScheduled;
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class QuerySentResult {
|
||||||
|
private final int transactionId;
|
||||||
|
private final List<String> subTypes = new ArrayList<>();
|
||||||
|
private final ScheduledQueryTaskArgs taskArgs;
|
||||||
|
|
||||||
|
QuerySentResult(int transactionId, @NonNull List<String> subTypes,
|
||||||
|
@NonNull ScheduledQueryTaskArgs taskArgs) {
|
||||||
|
this.transactionId = transactionId;
|
||||||
|
this.subTypes.addAll(subTypes);
|
||||||
|
this.taskArgs = taskArgs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A FutureTask that enqueues a single query, and schedule a new FutureTask for the next task.
|
||||||
|
private class QueryTask implements Runnable {
|
||||||
|
|
||||||
|
private final ScheduledQueryTaskArgs taskArgs;
|
||||||
|
private final List<MdnsResponse> servicesToResolve = new ArrayList<>();
|
||||||
|
private final boolean sendDiscoveryQueries;
|
||||||
|
|
||||||
|
QueryTask(@NonNull ScheduledQueryTaskArgs taskArgs,
|
||||||
|
@NonNull List<MdnsResponse> servicesToResolve, boolean sendDiscoveryQueries) {
|
||||||
|
this.taskArgs = taskArgs;
|
||||||
|
this.servicesToResolve.addAll(servicesToResolve);
|
||||||
|
this.sendDiscoveryQueries = sendDiscoveryQueries;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
final List<MdnsResponse> servicesToResolve;
|
|
||||||
final boolean sendDiscoveryQueries;
|
|
||||||
synchronized (lock) {
|
|
||||||
// The listener is requesting to resolve a service that has no info in
|
|
||||||
// cache. Use the provided name to generate a minimal response, so other records are
|
|
||||||
// queried to complete it.
|
|
||||||
servicesToResolve = makeResponsesForResolve(config.socketKey);
|
|
||||||
sendDiscoveryQueries = servicesToResolve.size() < listeners.size();
|
|
||||||
}
|
|
||||||
Pair<Integer, List<String>> result;
|
Pair<Integer, List<String>> result;
|
||||||
try {
|
try {
|
||||||
result =
|
result =
|
||||||
@@ -695,80 +769,27 @@ public class MdnsServiceTypeClient {
|
|||||||
socketClient,
|
socketClient,
|
||||||
createMdnsPacketWriter(),
|
createMdnsPacketWriter(),
|
||||||
serviceType,
|
serviceType,
|
||||||
config.subtypes,
|
taskArgs.config.subtypes,
|
||||||
config.expectUnicastResponse,
|
taskArgs.config.expectUnicastResponse,
|
||||||
config.transactionId,
|
taskArgs.config.transactionId,
|
||||||
config.socketKey,
|
taskArgs.config.socketKey,
|
||||||
config.onlyUseIpv6OnIpv6OnlyNetworks,
|
taskArgs.config.onlyUseIpv6OnIpv6OnlyNetworks,
|
||||||
sendDiscoveryQueries,
|
sendDiscoveryQueries,
|
||||||
servicesToResolve,
|
servicesToResolve,
|
||||||
clock)
|
clock)
|
||||||
.call();
|
.call();
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
sharedLog.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
|
sharedLog.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
|
||||||
TextUtils.join(",", config.subtypes)), e);
|
TextUtils.join(",", taskArgs.config.subtypes)), e);
|
||||||
result = null;
|
result = Pair.create(-1, new ArrayList<>());
|
||||||
}
|
|
||||||
synchronized (lock) {
|
|
||||||
if (MdnsConfigs.useSessionIdToScheduleMdnsTask()) {
|
|
||||||
// In case that the task is not canceled successfully, use session ID to check
|
|
||||||
// if this task should continue to schedule more.
|
|
||||||
if (sessionId != currentSessionId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((result != null)) {
|
|
||||||
for (int i = 0; i < listeners.size(); i++) {
|
|
||||||
listeners.keyAt(i).onDiscoveryQuerySent(result.second, result.first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shouldRemoveServiceAfterTtlExpires()) {
|
|
||||||
Iterator<MdnsResponse> iter = instanceNameToResponse.values().iterator();
|
|
||||||
while (iter.hasNext()) {
|
|
||||||
MdnsResponse existingResponse = iter.next();
|
|
||||||
if (existingResponse.hasServiceRecord()
|
|
||||||
&& existingResponse
|
|
||||||
.getServiceRecord()
|
|
||||||
.getRemainingTTL(clock.elapsedRealtime())
|
|
||||||
== 0) {
|
|
||||||
iter.remove();
|
|
||||||
for (int i = 0; i < listeners.size(); i++) {
|
|
||||||
if (!responseMatchesOptions(existingResponse,
|
|
||||||
listeners.valueAt(i))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final MdnsServiceBrowserListener listener = listeners.keyAt(i);
|
|
||||||
if (existingResponse.getServiceInstanceName() != null) {
|
|
||||||
final MdnsServiceInfo serviceInfo =
|
|
||||||
buildMdnsServiceInfoFromResponse(
|
|
||||||
existingResponse, serviceTypeLabels);
|
|
||||||
if (existingResponse.isComplete()) {
|
|
||||||
sharedLog.log("TTL expired. onServiceRemoved: "
|
|
||||||
+ serviceInfo);
|
|
||||||
listener.onServiceRemoved(serviceInfo);
|
|
||||||
}
|
|
||||||
sharedLog.log("TTL expired. onServiceNameRemoved: "
|
|
||||||
+ serviceInfo);
|
|
||||||
listener.onServiceNameRemoved(serviceInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QueryTaskConfig nextRunConfig = this.config.getConfigForNextRun();
|
|
||||||
final long now = clock.elapsedRealtime();
|
|
||||||
lastSentTime = now;
|
|
||||||
final long minRemainingTtl = getMinRemainingTtlLocked(now);
|
|
||||||
final long timeToRun = calculateTimeToRun(this, nextRunConfig, now,
|
|
||||||
minRemainingTtl, lastSentTime);
|
|
||||||
scheduleNextRunLocked(nextRunConfig, minRemainingTtl, now, timeToRun,
|
|
||||||
lastScheduledTask.sessionId);
|
|
||||||
}
|
}
|
||||||
|
dependencies.sendMessage(
|
||||||
|
handler, handler.obtainMessage(EVENT_QUERY_RESULT,
|
||||||
|
new QuerySentResult(result.first, result.second, taskArgs)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long calculateTimeToRun(@NonNull QueryTask lastScheduledTask,
|
private static long calculateTimeToRun(@NonNull ScheduledQueryTaskArgs taskArgs,
|
||||||
QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime) {
|
QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime) {
|
||||||
final long baseDelayInMs = queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
|
final long baseDelayInMs = queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
|
||||||
if (!queryTaskConfig.shouldUseQueryBackoff()) {
|
if (!queryTaskConfig.shouldUseQueryBackoff()) {
|
||||||
@@ -781,16 +802,15 @@ public class MdnsServiceTypeClient {
|
|||||||
}
|
}
|
||||||
// If the next TTL expiration time hasn't changed, then use previous calculated timeToRun.
|
// If the next TTL expiration time hasn't changed, then use previous calculated timeToRun.
|
||||||
if (lastSentTime < now
|
if (lastSentTime < now
|
||||||
&& lastScheduledTask.minTtlExpirationTimeWhenScheduled == now + minRemainingTtl) {
|
&& taskArgs.minTtlExpirationTimeWhenScheduled == now + minRemainingTtl) {
|
||||||
// Use the original scheduling time if the TTL has not changed, to avoid continuously
|
// Use the original scheduling time if the TTL has not changed, to avoid continuously
|
||||||
// rescheduling to 80% of the remaining TTL as time passes
|
// rescheduling to 80% of the remaining TTL as time passes
|
||||||
return lastScheduledTask.timeToRun;
|
return taskArgs.timeToRun;
|
||||||
}
|
}
|
||||||
return Math.max(now + (long) (0.8 * minRemainingTtl), lastSentTime + baseDelayInMs);
|
return Math.max(now + (long) (0.8 * minRemainingTtl), lastSentTime + baseDelayInMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("lock")
|
private long getMinRemainingTtl(long now) {
|
||||||
private long getMinRemainingTtlLocked(long now) {
|
|
||||||
long minRemainingTtl = Long.MAX_VALUE;
|
long minRemainingTtl = Long.MAX_VALUE;
|
||||||
for (MdnsResponse response : instanceNameToResponse.values()) {
|
for (MdnsResponse response : instanceNameToResponse.values()) {
|
||||||
if (!response.isComplete()) {
|
if (!response.isComplete()) {
|
||||||
@@ -811,19 +831,18 @@ public class MdnsServiceTypeClient {
|
|||||||
|
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
@NonNull
|
@NonNull
|
||||||
private void scheduleNextRunLocked(@NonNull QueryTaskConfig nextRunConfig,
|
private void scheduleNextRun(@NonNull QueryTaskConfig nextRunConfig,
|
||||||
long minRemainingTtl,
|
long minRemainingTtl,
|
||||||
long timeWhenScheduled, long timeToRun, long sessionId) {
|
long timeWhenScheduled, long timeToRun, long sessionId) {
|
||||||
lastScheduledTask = new QueryTask(nextRunConfig, timeToRun,
|
lastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(nextRunConfig, timeToRun,
|
||||||
minRemainingTtl + timeWhenScheduled, sessionId);
|
minRemainingTtl + timeWhenScheduled, sessionId);
|
||||||
// The timeWhenScheduled could be greater than the timeToRun if the Runnable is delayed.
|
// The timeWhenScheduled could be greater than the timeToRun if the Runnable is delayed.
|
||||||
long timeToNextTasksWithBackoffInMs = Math.max(timeToRun - timeWhenScheduled, 0);
|
long timeToNextTasksWithBackoffInMs = Math.max(timeToRun - timeWhenScheduled, 0);
|
||||||
sharedLog.log(
|
sharedLog.log(String.format("Next run: sessionId: %d, in %d ms",
|
||||||
String.format("Next run: sessionId: %d, in %d ms", lastScheduledTask.sessionId,
|
lastScheduledQueryTaskArgs.sessionId, timeToNextTasksWithBackoffInMs));
|
||||||
timeToNextTasksWithBackoffInMs));
|
|
||||||
dependencies.sendMessageDelayed(
|
dependencies.sendMessageDelayed(
|
||||||
handler,
|
handler,
|
||||||
handler.obtainMessage(EVENT_START_QUERYTASK, lastScheduledTask),
|
handler.obtainMessage(EVENT_START_QUERYTASK, lastScheduledQueryTaskArgs),
|
||||||
timeToNextTasksWithBackoffInMs);
|
timeToNextTasksWithBackoffInMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,6 +204,13 @@ public class MdnsServiceTypeClientTests {
|
|||||||
return true;
|
return true;
|
||||||
}).when(mockDeps).sendMessageDelayed(any(Handler.class), any(Message.class), anyLong());
|
}).when(mockDeps).sendMessageDelayed(any(Handler.class), any(Message.class), anyLong());
|
||||||
|
|
||||||
|
doAnswer(inv -> {
|
||||||
|
final Handler handler = (Handler) inv.getArguments()[0];
|
||||||
|
final Message message = (Message) inv.getArguments()[1];
|
||||||
|
runOnHandler(() -> handler.dispatchMessage(message));
|
||||||
|
return true;
|
||||||
|
}).when(mockDeps).sendMessage(any(Handler.class), any(Message.class));
|
||||||
|
|
||||||
client =
|
client =
|
||||||
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
|
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
|
||||||
mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps) {
|
mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps) {
|
||||||
@@ -925,6 +932,7 @@ public class MdnsServiceTypeClientTests {
|
|||||||
// Simulate the case where the response is under TTL.
|
// Simulate the case where the response is under TTL.
|
||||||
doReturn(TEST_ELAPSED_REALTIME + TEST_TTL - 1L).when(mockDecoderClock).elapsedRealtime();
|
doReturn(TEST_ELAPSED_REALTIME + TEST_TTL - 1L).when(mockDecoderClock).elapsedRealtime();
|
||||||
firstMdnsTask.run();
|
firstMdnsTask.run();
|
||||||
|
verify(mockDeps, times(1)).sendMessage(any(), any(Message.class));
|
||||||
|
|
||||||
// Verify removed callback was not called.
|
// Verify removed callback was not called.
|
||||||
verifyServiceRemovedNoCallback(mockListenerOne);
|
verifyServiceRemovedNoCallback(mockListenerOne);
|
||||||
@@ -932,6 +940,7 @@ public class MdnsServiceTypeClientTests {
|
|||||||
// Simulate the case where the response is after TTL.
|
// Simulate the case where the response is after TTL.
|
||||||
doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
|
doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
|
||||||
firstMdnsTask.run();
|
firstMdnsTask.run();
|
||||||
|
verify(mockDeps, times(2)).sendMessage(any(), any(Message.class));
|
||||||
|
|
||||||
// Verify removed callback was called.
|
// Verify removed callback was called.
|
||||||
verifyServiceRemovedCallback(
|
verifyServiceRemovedCallback(
|
||||||
@@ -1118,6 +1127,7 @@ public class MdnsServiceTypeClientTests {
|
|||||||
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
|
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
|
||||||
srvTxtQueryCaptor.capture(),
|
srvTxtQueryCaptor.capture(),
|
||||||
eq(socketKey), eq(false));
|
eq(socketKey), eq(false));
|
||||||
|
verify(mockDeps, times(1)).sendMessage(any(), any(Message.class));
|
||||||
assertNotNull(delayMessage);
|
assertNotNull(delayMessage);
|
||||||
|
|
||||||
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
|
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
|
||||||
@@ -1210,6 +1220,7 @@ public class MdnsServiceTypeClientTests {
|
|||||||
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
|
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingUnicastResponse(
|
||||||
srvTxtQueryCaptor.capture(),
|
srvTxtQueryCaptor.capture(),
|
||||||
eq(socketKey), eq(false));
|
eq(socketKey), eq(false));
|
||||||
|
verify(mockDeps, times(1)).sendMessage(any(), any(Message.class));
|
||||||
assertNotNull(delayMessage);
|
assertNotNull(delayMessage);
|
||||||
|
|
||||||
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
|
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
|
||||||
@@ -1249,6 +1260,7 @@ public class MdnsServiceTypeClientTests {
|
|||||||
// Advance time so 75% of TTL passes and re-execute
|
// Advance time so 75% of TTL passes and re-execute
|
||||||
doReturn(TEST_ELAPSED_REALTIME + (long) (TEST_TTL * 0.75))
|
doReturn(TEST_ELAPSED_REALTIME + (long) (TEST_TTL * 0.75))
|
||||||
.when(mockDecoderClock).elapsedRealtime();
|
.when(mockDecoderClock).elapsedRealtime();
|
||||||
|
verify(mockDeps, times(2)).sendMessage(any(), any(Message.class));
|
||||||
assertNotNull(delayMessage);
|
assertNotNull(delayMessage);
|
||||||
dispatchMessage();
|
dispatchMessage();
|
||||||
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
|
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
|
||||||
@@ -1260,12 +1272,13 @@ public class MdnsServiceTypeClientTests {
|
|||||||
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
|
inOrder.verify(mockSocketClient, times(2)).sendPacketRequestingMulticastResponse(
|
||||||
renewalQueryCaptor.capture(),
|
renewalQueryCaptor.capture(),
|
||||||
eq(socketKey), eq(false));
|
eq(socketKey), eq(false));
|
||||||
|
verify(mockDeps, times(3)).sendMessage(any(), any(Message.class));
|
||||||
|
assertNotNull(delayMessage);
|
||||||
inOrder.verify(mockListenerOne).onDiscoveryQuerySent(any(), anyInt());
|
inOrder.verify(mockListenerOne).onDiscoveryQuerySent(any(), anyInt());
|
||||||
final MdnsPacket renewalPacket = MdnsPacket.parse(
|
final MdnsPacket renewalPacket = MdnsPacket.parse(
|
||||||
new MdnsPacketReader(renewalQueryCaptor.getValue()));
|
new MdnsPacketReader(renewalQueryCaptor.getValue()));
|
||||||
assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_ANY, serviceName));
|
assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_ANY, serviceName));
|
||||||
inOrder.verifyNoMoreInteractions();
|
inOrder.verifyNoMoreInteractions();
|
||||||
assertNotNull(delayMessage);
|
|
||||||
|
|
||||||
long updatedReceiptTime = TEST_ELAPSED_REALTIME + TEST_TTL;
|
long updatedReceiptTime = TEST_ELAPSED_REALTIME + TEST_TTL;
|
||||||
final MdnsPacket refreshedSrvTxtResponse = new MdnsPacket(
|
final MdnsPacket refreshedSrvTxtResponse = new MdnsPacket(
|
||||||
@@ -1545,6 +1558,8 @@ public class MdnsServiceTypeClientTests {
|
|||||||
expectedIPv6Packets[index], socketKey, false);
|
expectedIPv6Packets[index], socketKey, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
verify(mockDeps, times(index + 1))
|
||||||
|
.sendMessage(any(Handler.class), any(Message.class));
|
||||||
// Verify the task has been scheduled.
|
// Verify the task has been scheduled.
|
||||||
verify(mockDeps, times(scheduledCount))
|
verify(mockDeps, times(scheduledCount))
|
||||||
.sendMessageDelayed(any(Handler.class), any(Message.class), anyLong());
|
.sendMessageDelayed(any(Handler.class), any(Message.class), anyLong());
|
||||||
|
|||||||
Reference in New Issue
Block a user