Use timerfd to improve the accuracy of query timing
Timerfd can provide more accurate delayed callbacks than
Handler#postDelayed because it accounts for the time the device
spends in deep sleep. Using timerfd allows for more precise query
timing, which reduces the possibility of TTL expiration caused
by inaccurate delayed callbacks.
Bug: 366373064
Test: atest FrameworksNetTests NsdManagerTest
Change-Id: I980ec8fd43f63976ef5387bb57c7ed70a1b68d82
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
index 4e27fef..c4a9110 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -89,6 +89,11 @@
"nsd_cached_services_retention_time";
public static final int DEFAULT_CACHED_SERVICES_RETENTION_TIME_MILLISECONDS = 10000;
+ /**
+ * A feature flag to control whether the accurate delay callback should be enabled.
+ */
+ public static final String NSD_ACCURATE_DELAY_CALLBACK = "nsd_accurate_delay_callback";
+
// Flag for offload feature
public final boolean mIsMdnsOffloadFeatureEnabled;
@@ -122,6 +127,9 @@
// Retention Time for cached services
public final long mCachedServicesRetentionTime;
+ // Flag for accurate delay callback
+ public final boolean mIsAccurateDelayCallbackEnabled;
+
@Nullable
private final FlagOverrideProvider mOverrideProvider;
@@ -218,6 +226,14 @@
}
/**
+ * Indicates whether {@link #NSD_ACCURATE_DELAY_CALLBACK} is enabled, including for testing.
+ */
+ public boolean isAccurateDelayCallbackEnabled() {
+ return mIsAccurateDelayCallbackEnabled
+ || isForceEnabledForTest(NSD_ACCURATE_DELAY_CALLBACK);
+ }
+
+ /**
* The constructor for {@link MdnsFeatureFlags}.
*/
public MdnsFeatureFlags(boolean isOffloadFeatureEnabled,
@@ -231,6 +247,7 @@
boolean avoidAdvertisingEmptyTxtRecords,
boolean isCachedServicesRemovalEnabled,
long cachedServicesRetentionTime,
+ boolean isAccurateDelayCallbackEnabled,
@Nullable FlagOverrideProvider overrideProvider) {
mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
@@ -243,6 +260,7 @@
mAvoidAdvertisingEmptyTxtRecords = avoidAdvertisingEmptyTxtRecords;
mIsCachedServicesRemovalEnabled = isCachedServicesRemovalEnabled;
mCachedServicesRetentionTime = cachedServicesRetentionTime;
+ mIsAccurateDelayCallbackEnabled = isAccurateDelayCallbackEnabled;
mOverrideProvider = overrideProvider;
}
@@ -266,6 +284,7 @@
private boolean mAvoidAdvertisingEmptyTxtRecords;
private boolean mIsCachedServicesRemovalEnabled;
private long mCachedServicesRetentionTime;
+ private boolean mIsAccurateDelayCallbackEnabled;
private FlagOverrideProvider mOverrideProvider;
/**
@@ -283,6 +302,7 @@
mAvoidAdvertisingEmptyTxtRecords = true; // Default enabled.
mIsCachedServicesRemovalEnabled = false;
mCachedServicesRetentionTime = DEFAULT_CACHED_SERVICES_RETENTION_TIME_MILLISECONDS;
+ mIsAccurateDelayCallbackEnabled = false;
mOverrideProvider = null;
}
@@ -409,6 +429,16 @@
}
/**
+ * Set whether the accurate delay callback is enabled.
+ *
+ * @see #NSD_ACCURATE_DELAY_CALLBACK
+ */
+ public Builder setIsAccurateDelayCallbackEnabled(boolean isAccurateDelayCallbackEnabled) {
+ mIsAccurateDelayCallbackEnabled = isAccurateDelayCallbackEnabled;
+ return this;
+ }
+
+ /**
* Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
*/
public MdnsFeatureFlags build() {
@@ -423,6 +453,7 @@
mAvoidAdvertisingEmptyTxtRecords,
mIsCachedServicesRemovalEnabled,
mCachedServicesRetentionTime,
+ mIsAccurateDelayCallbackEnabled,
mOverrideProvider);
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index a43486e..7a93fec 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -20,6 +20,7 @@
import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE;
import static com.android.server.connectivity.mdns.MdnsServiceCache.ServiceExpiredCallback;
import static com.android.server.connectivity.mdns.MdnsServiceCache.findMatchedResponse;
+import static com.android.server.connectivity.mdns.MdnsQueryScheduler.ScheduledQueryTaskArgs;
import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
import static com.android.server.connectivity.mdns.util.MdnsUtils.buildMdnsServiceInfoFromResponse;
@@ -37,6 +38,7 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.DnsUtils;
import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.TimerFileDescriptor;
import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
@@ -94,6 +96,9 @@
private final boolean removeServiceAfterTtlExpires =
MdnsConfigs.removeServiceAfterTtlExpires();
private final Clock clock;
+ // Use TimerFileDescriptor for query scheduling, which allows for more accurate sending of
+ // queries.
+ @NonNull private final TimerFileDescriptor timerFd;
@Nullable private MdnsSearchOptions searchOptions;
@@ -139,8 +144,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_START_QUERYTASK: {
- final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs =
- (MdnsQueryScheduler.ScheduledQueryTaskArgs) 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
@@ -174,7 +178,7 @@
final long now = clock.elapsedRealtime();
lastSentTime = now;
final long minRemainingTtl = getMinRemainingTtl(now);
- MdnsQueryScheduler.ScheduledQueryTaskArgs args =
+ final ScheduledQueryTaskArgs args =
mdnsQueryScheduler.scheduleNextRun(
sentResult.taskArgs.config,
minRemainingTtl,
@@ -189,10 +193,14 @@
sharedLog.log(String.format("Query sent with transactionId: %d. "
+ "Next run: sessionId: %d, in %d ms",
sentResult.transactionId, args.sessionId, timeToNextTaskMs));
- dependencies.sendMessageDelayed(
- handler,
- handler.obtainMessage(EVENT_START_QUERYTASK, args),
- timeToNextTaskMs);
+ if (featureFlags.isAccurateDelayCallbackEnabled()) {
+ setDelayedTask(args, timeToNextTaskMs);
+ } else {
+ dependencies.sendMessageDelayed(
+ handler,
+ handler.obtainMessage(EVENT_START_QUERYTASK, args),
+ timeToNextTaskMs);
+ }
break;
}
default:
@@ -254,6 +262,14 @@
return List.of(new DatagramPacket(queryBuffer, 0, queryBuffer.length, address));
}
}
+
+ /**
+ * @see TimerFileDescriptor
+ */
+ @Nullable
+ public TimerFileDescriptor createTimerFd(@NonNull Handler handler) {
+ return new TimerFileDescriptor(handler);
+ }
}
/**
@@ -301,6 +317,7 @@
this.mdnsQueryScheduler = new MdnsQueryScheduler();
this.cacheKey = new MdnsServiceCache.CacheKey(serviceType, socketKey);
this.featureFlags = featureFlags;
+ this.timerFd = dependencies.createTimerFd(handler);
}
/**
@@ -317,6 +334,13 @@
? serviceCache.getCachedServices(cacheKey) : Collections.emptyList();
}
+ private void setDelayedTask(ScheduledQueryTaskArgs args, long timeToNextTaskMs) {
+ timerFd.cancelTask();
+ timerFd.setDelayedTask(new TimerFileDescriptor.MessageTask(
+ handler.obtainMessage(EVENT_START_QUERYTASK, args)),
+ timeToNextTaskMs);
+ }
+
/**
* Registers {@code listener} for receiving discovery event of mDNS service instances, and
* starts
@@ -363,7 +387,7 @@
}
final long minRemainingTtl = getMinRemainingTtl(now);
if (hadReply) {
- MdnsQueryScheduler.ScheduledQueryTaskArgs args =
+ final ScheduledQueryTaskArgs args =
mdnsQueryScheduler.scheduleNextRun(
taskConfig,
minRemainingTtl,
@@ -377,10 +401,14 @@
final long timeToNextTaskMs = calculateTimeToNextTask(args, now);
sharedLog.log(String.format("Schedule a query. Next run: sessionId: %d, in %d ms",
args.sessionId, timeToNextTaskMs));
- dependencies.sendMessageDelayed(
- handler,
- handler.obtainMessage(EVENT_START_QUERYTASK, args),
- timeToNextTaskMs);
+ if (featureFlags.isAccurateDelayCallbackEnabled()) {
+ setDelayedTask(args, timeToNextTaskMs);
+ } else {
+ dependencies.sendMessageDelayed(
+ handler,
+ handler.obtainMessage(EVENT_START_QUERYTASK, args),
+ timeToNextTaskMs);
+ }
} else {
final List<MdnsResponse> servicesToResolve = makeResponsesForResolve(socketKey);
final QueryTask queryTask = new QueryTask(
@@ -420,7 +448,11 @@
}
private void removeScheduledTask() {
- dependencies.removeMessages(handler, EVENT_START_QUERYTASK);
+ if (featureFlags.isAccurateDelayCallbackEnabled()) {
+ timerFd.cancelTask();
+ } else {
+ dependencies.removeMessages(handler, EVENT_START_QUERYTASK);
+ }
sharedLog.log("Remove EVENT_START_QUERYTASK"
+ ", current session: " + currentSessionId);
++currentSessionId;
@@ -506,10 +538,13 @@
}
}
}
- if (dependencies.hasMessages(handler, EVENT_START_QUERYTASK)) {
+ final boolean hasScheduledTask = featureFlags.isAccurateDelayCallbackEnabled()
+ ? timerFd.hasDelayedTask()
+ : dependencies.hasMessages(handler, EVENT_START_QUERYTASK);
+ if (hasScheduledTask) {
final long now = clock.elapsedRealtime();
final long minRemainingTtl = getMinRemainingTtl(now);
- MdnsQueryScheduler.ScheduledQueryTaskArgs args =
+ final ScheduledQueryTaskArgs args =
mdnsQueryScheduler.maybeRescheduleCurrentRun(now, minRemainingTtl,
lastSentTime, currentSessionId + 1,
searchOptions.numOfQueriesBeforeBackoff());
@@ -518,10 +553,14 @@
final long timeToNextTaskMs = calculateTimeToNextTask(args, now);
sharedLog.log(String.format("Reschedule a query. Next run: sessionId: %d, in %d ms",
args.sessionId, timeToNextTaskMs));
- dependencies.sendMessageDelayed(
- handler,
- handler.obtainMessage(EVENT_START_QUERYTASK, args),
- timeToNextTaskMs);
+ if (featureFlags.isAccurateDelayCallbackEnabled()) {
+ setDelayedTask(args, timeToNextTaskMs);
+ } else {
+ dependencies.sendMessageDelayed(
+ handler,
+ handler.obtainMessage(EVENT_START_QUERYTASK, args),
+ timeToNextTaskMs);
+ }
}
}
}
@@ -686,10 +725,10 @@
private static class QuerySentArguments {
private final int transactionId;
private final List<String> subTypes = new ArrayList<>();
- private final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs;
+ private final ScheduledQueryTaskArgs taskArgs;
QuerySentArguments(int transactionId, @NonNull List<String> subTypes,
- @NonNull MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs) {
+ @NonNull ScheduledQueryTaskArgs taskArgs) {
this.transactionId = transactionId;
this.subTypes.addAll(subTypes);
this.taskArgs = taskArgs;
@@ -698,14 +737,14 @@
// A FutureTask that enqueues a single query, and schedule a new FutureTask for the next task.
private class QueryTask implements Runnable {
- private final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs;
+ private final ScheduledQueryTaskArgs taskArgs;
private final List<MdnsResponse> servicesToResolve = new ArrayList<>();
private final List<String> subtypes = new ArrayList<>();
private final boolean sendDiscoveryQueries;
private final List<MdnsResponse> existingServices = new ArrayList<>();
private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
private final SocketKey socketKey;
- QueryTask(@NonNull MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs,
+ QueryTask(@NonNull ScheduledQueryTaskArgs taskArgs,
@NonNull Collection<MdnsResponse> servicesToResolve,
@NonNull Collection<String> subtypes, boolean sendDiscoveryQueries,
@NonNull Collection<MdnsResponse> existingServices,
@@ -771,7 +810,7 @@
return minRemainingTtl == Long.MAX_VALUE ? 0 : minRemainingTtl;
}
- private static long calculateTimeToNextTask(MdnsQueryScheduler.ScheduledQueryTaskArgs args,
+ private static long calculateTimeToNextTask(ScheduledQueryTaskArgs args,
long now) {
return Math.max(args.timeToRun - now, 0);
}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index b5ee789..046b035 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -438,7 +438,10 @@
srcs: [
"device/com/android/net/module/util/FdEventsReader.java",
"device/com/android/net/module/util/HandlerUtils.java",
+ "device/com/android/net/module/util/JniUtil.java",
"device/com/android/net/module/util/SharedLog.java",
+ "device/com/android/net/module/util/TimerFdUtils.java",
+ "device/com/android/net/module/util/TimerFileDescriptor.java",
"framework/com/android/net/module/util/ByteUtils.java",
"framework/com/android/net/module/util/CollectionUtils.java",
"framework/com/android/net/module/util/DnsUtils.java",
diff --git a/staticlibs/device/com/android/net/module/util/TimerFileDescriptor.java b/staticlibs/device/com/android/net/module/util/TimerFileDescriptor.java
index dbbccc5..a8c0f17 100644
--- a/staticlibs/device/com/android/net/module/util/TimerFileDescriptor.java
+++ b/staticlibs/device/com/android/net/module/util/TimerFileDescriptor.java
@@ -103,6 +103,13 @@
public void post(Handler handler) {
handler.sendMessage(mMessage);
}
+
+ /**
+ * Get scheduled message
+ */
+ public Message getMessage() {
+ return mMessage;
+ }
}
/**
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index da0bc88..ed95e4b 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -60,6 +60,7 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.TimerFileDescriptor;
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
import com.android.server.connectivity.mdns.util.MdnsUtils;
import com.android.testutils.DevSdkIgnoreRule;
@@ -127,6 +128,8 @@
private SharedLog mockSharedLog;
@Mock
private MdnsServiceTypeClient.Dependencies mockDeps;
+ @Mock
+ private TimerFileDescriptor mockTimerFd;
@Captor
private ArgumentCaptor<MdnsServiceInfo> serviceInfoCaptor;
@@ -145,6 +148,7 @@
private Message delayMessage = null;
private Handler realHandler = null;
private MdnsFeatureFlags featureFlags = MdnsFeatureFlags.newBuilder().build();
+ private TimerFileDescriptor.MessageTask task = null;
@Before
@SuppressWarnings("DoNotMock")
@@ -244,10 +248,21 @@
return true;
}).when(mockDeps).sendMessage(any(Handler.class), any(Message.class));
- client = makeMdnsServiceTypeClient();
+ doAnswer(inv -> {
+ realHandler = (Handler) inv.getArguments()[0];
+ return mockTimerFd;
+ }).when(mockDeps).createTimerFd(any(Handler.class));
+
+ doAnswer(inv -> {
+ task = (TimerFileDescriptor.MessageTask) inv.getArguments()[0];
+ latestDelayMs = (long) inv.getArguments()[1];
+ return null;
+ }).when(mockTimerFd).setDelayedTask(any(), anyLong());
+
+ client = makeMdnsServiceTypeClient(featureFlags);
}
- private MdnsServiceTypeClient makeMdnsServiceTypeClient() {
+ private MdnsServiceTypeClient makeMdnsServiceTypeClient(MdnsFeatureFlags featureFlags) {
return new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
serviceCache, featureFlags);
@@ -1926,9 +1941,7 @@
@Test
public void testSendQueryWithKnownAnswers() throws Exception {
- client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
- serviceCache,
+ client = makeMdnsServiceTypeClient(
MdnsFeatureFlags.newBuilder().setIsQueryWithKnownAnswerEnabled(true).build());
doCallRealMethod().when(mockDeps).getDatagramPacketsFromMdnsPacket(
@@ -1990,9 +2003,7 @@
@Test
public void testSendQueryWithSubTypeWithKnownAnswers() throws Exception {
- client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
- mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
- serviceCache,
+ client = makeMdnsServiceTypeClient(
MdnsFeatureFlags.newBuilder().setIsQueryWithKnownAnswerEnabled(true).build());
doCallRealMethod().when(mockDeps).getDatagramPacketsFromMdnsPacket(
@@ -2114,6 +2125,66 @@
assertEquals(9680L, latestDelayMs);
}
+ @Test
+ public void sendQueries_AccurateDelayCallback() {
+ client = makeMdnsServiceTypeClient(
+ MdnsFeatureFlags.newBuilder().setIsAccurateDelayCallbackEnabled(true).build());
+
+ final int numOfQueriesBeforeBackoff = 2;
+ final MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
+ .addSubtype(SUBTYPE)
+ .setQueryMode(AGGRESSIVE_QUERY_MODE)
+ .setNumOfQueriesBeforeBackoff(numOfQueriesBeforeBackoff)
+ .build();
+ startSendAndReceive(mockListenerOne, searchOptions);
+ verify(mockTimerFd, times(1)).cancelTask();
+
+ // Verify that the first query has been sent.
+ verifyAndSendQuery(0 /* index */, 0 /* timeInMs */, true /* expectsUnicastResponse */,
+ true /* multipleSocketDiscovery */, 1 /* scheduledCount */,
+ 1 /* sendMessageCount */, true /* useAccurateDelayCallback */);
+ // Verify that the task cancellation occurred before scheduling another query.
+ verify(mockTimerFd, times(2)).cancelTask();
+
+ // Verify that the second query has been sent
+ verifyAndSendQuery(1 /* index */, 0 /* timeInMs */, false /* expectsUnicastResponse */,
+ true /* multipleSocketDiscovery */, 2 /* scheduledCount */,
+ 2 /* sendMessageCount */, true /* useAccurateDelayCallback */);
+ // Verify that the task cancellation occurred before scheduling another query.
+ verify(mockTimerFd, times(3)).cancelTask();
+
+ // Verify that the third query has been sent
+ verifyAndSendQuery(2 /* index */, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS,
+ false /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
+ 3 /* scheduledCount */, 3 /* sendMessageCount */,
+ true /* useAccurateDelayCallback */);
+ // Verify that the task cancellation occurred before scheduling another query.
+ verify(mockTimerFd, times(4)).cancelTask();
+
+ // In backoff mode, the current scheduled task will be canceled and reschedule if the
+ // 0.8 * smallestRemainingTtl is larger than time to next run.
+ long currentTime = TEST_TTL / 2 + TEST_ELAPSED_REALTIME;
+ doReturn(currentTime).when(mockDecoderClock).elapsedRealtime();
+ doReturn(true).when(mockTimerFd).hasDelayedTask();
+ processResponse(createResponse(
+ "service-instance-1", "192.0.2.123", 5353,
+ SERVICE_TYPE_LABELS,
+ Collections.emptyMap(), TEST_TTL), socketKey);
+ // Verify that the task cancellation occurred twice.
+ verify(mockTimerFd, times(6)).cancelTask();
+ assertNotNull(task);
+ verifyAndSendQuery(3 /* index */, (long) (TEST_TTL / 2 * 0.8) /* timeInMs */,
+ true /* expectsUnicastResponse */, true /* multipleSocketDiscovery */,
+ 5 /* scheduledCount */, 4 /* sendMessageCount */,
+ true /* useAccurateDelayCallback */);
+ // Verify that the task cancellation occurred before scheduling another query.
+ verify(mockTimerFd, times(7)).cancelTask();
+
+ // Stop sending packets.
+ stopSendAndReceive(mockListenerOne);
+ verify(mockTimerFd, times(8)).cancelTask();
+ }
+
private static MdnsServiceInfo matchServiceName(String name) {
return argThat(info -> info.getServiceInstanceName().equals(name));
}
@@ -2127,9 +2198,22 @@
private void verifyAndSendQuery(int index, long timeInMs, boolean expectsUnicastResponse,
boolean multipleSocketDiscovery, int scheduledCount) {
- // Dispatch the message
- if (delayMessage != null && realHandler != null) {
- dispatchMessage();
+ verifyAndSendQuery(index, timeInMs, expectsUnicastResponse,
+ multipleSocketDiscovery, scheduledCount, index + 1 /* sendMessageCount */,
+ false /* useAccurateDelayCallback */);
+ }
+
+ private void verifyAndSendQuery(int index, long timeInMs, boolean expectsUnicastResponse,
+ boolean multipleSocketDiscovery, int scheduledCount, int sendMessageCount,
+ boolean useAccurateDelayCallback) {
+ if (useAccurateDelayCallback && task != null && realHandler != null) {
+ runOnHandler(() -> realHandler.dispatchMessage(task.getMessage()));
+ task = null;
+ } else {
+ // Dispatch the message
+ if (delayMessage != null && realHandler != null) {
+ dispatchMessage();
+ }
}
assertEquals(timeInMs, latestDelayMs);
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
@@ -2152,11 +2236,15 @@
eq(socketKey), eq(false));
}
}
- verify(mockDeps, times(index + 1))
+ verify(mockDeps, times(sendMessageCount))
.sendMessage(any(Handler.class), any(Message.class));
// Verify the task has been scheduled.
- verify(mockDeps, times(scheduledCount))
- .sendMessageDelayed(any(Handler.class), any(Message.class), anyLong());
+ if (useAccurateDelayCallback) {
+ verify(mockTimerFd, times(scheduledCount)).setDelayedTask(any(), anyLong());
+ } else {
+ verify(mockDeps, times(scheduledCount))
+ .sendMessageDelayed(any(Handler.class), any(Message.class), anyLong());
+ }
}
private static String[] getTestServiceName(String instanceName) {