Skip to content

Commit 7cdd4ea

Browse files
toniheicopybara-github
authored andcommitted
Add max time diff setting for auto-advancing FakeClock
The auto advancing feature assumes there is a regular driver that pushes the time forward in small-ish increments (as done in ExoPlayer). This logic can break if: - There is process generating new messages out of control of the clock (e.g. a Loader in ExoPlayer) - The message queue has another timed message much further in the future that was never meant to drive the clock forward. This issue can be avoided by imposing a maximum expected time diff for the clock auto-advancing mechanism. Choosing 1 second by default should work well for all (Exo)Player use cases even with dynamic scheduling of updates. Also adding a Builder to make this configurable if required. PiperOrigin-RevId: 784194600
1 parent 8c2593a commit 7cdd4ea

File tree

3 files changed

+141
-6
lines changed

3 files changed

+141
-6
lines changed

RELEASENOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@
102102
* Stop enforcing a non-null mime type in `DefaultMediaItemConverter`.
103103
* Enable remote to local transfers in `DefaultCastOptionsProvider`.
104104
* Test Utilities:
105+
* Add maximum time diff for the auto-advancing behavior of `FakeClock`. It
106+
defaults to 1 second, but is configurable via `FakeClock.Builder`.
105107
* Remove deprecated symbols:
106108

107109
## 1.8

libraries/test_utils/src/main/java/androidx/media3/test/utils/FakeClock.java

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.google.common.collect.ComparisonChain;
3232
import com.google.common.collect.ImmutableSet;
3333
import com.google.common.collect.Ordering;
34+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
3435
import java.util.ArrayList;
3536
import java.util.Collections;
3637
import java.util.HashSet;
@@ -55,6 +56,95 @@
5556
@UnstableApi
5657
public class FakeClock implements Clock {
5758

59+
/** A builder for {@link FakeClock} instances. */
60+
public static final class Builder {
61+
private long bootTimeMs;
62+
private long initialTimeMs;
63+
private boolean isAutoAdvancing;
64+
private long maxAutoAdvancingTimeDiffMs;
65+
66+
/** Creates a new builder for {@link FakeClock} instances. */
67+
public Builder() {
68+
bootTimeMs = 0;
69+
initialTimeMs = 0;
70+
isAutoAdvancing = false;
71+
maxAutoAdvancingTimeDiffMs = DEFAULT_MAX_AUTO_ADVANCING_TIME_DIFF_MS;
72+
}
73+
74+
/**
75+
* Sets the time the system was booted since the Unix Epoch, in milliseconds.
76+
*
77+
* <p>The default value is 0.
78+
*
79+
* @param bootTimeMs The time the system was booted since the Unix Epoch, in milliseconds.
80+
* @return This builder.
81+
*/
82+
@CanIgnoreReturnValue
83+
public Builder setBootTimeMs(long bootTimeMs) {
84+
this.bootTimeMs = bootTimeMs;
85+
return this;
86+
}
87+
88+
/**
89+
* Sets the initial elapsed time since the boot time, in milliseconds.
90+
*
91+
* <p>The default value is 0.
92+
*
93+
* @param initialTimeMs The initial elapsed time since the boot time, in milliseconds.
94+
* @return This builder.
95+
*/
96+
@CanIgnoreReturnValue
97+
public Builder setInitialTimeMs(long initialTimeMs) {
98+
this.initialTimeMs = initialTimeMs;
99+
return this;
100+
}
101+
102+
/**
103+
* Sets whether the clock should automatically advance the time to the time of the next message
104+
* that is due to be sent.
105+
*
106+
* <p>The default value is false.
107+
*
108+
* @param isAutoAdvancing Whether the clock should automatically advance the time.
109+
* @return This builder.
110+
*/
111+
@CanIgnoreReturnValue
112+
public Builder setIsAutoAdvancing(boolean isAutoAdvancing) {
113+
this.isAutoAdvancing = isAutoAdvancing;
114+
return this;
115+
}
116+
117+
/**
118+
* Sets the maximum time difference between two messages that the fake clock will automatically
119+
* advance.
120+
*
121+
* <p>The default value is {@link #DEFAULT_MAX_AUTO_ADVANCING_TIME_DIFF_MS}.
122+
*
123+
* @param maxAutoAdvancingTimeDiffMs The maximum time difference in milliseconds.
124+
* @return This builder.
125+
*/
126+
@CanIgnoreReturnValue
127+
public Builder setMaxAutoAdvancingTimeDiffMs(long maxAutoAdvancingTimeDiffMs) {
128+
this.maxAutoAdvancingTimeDiffMs = maxAutoAdvancingTimeDiffMs;
129+
return this;
130+
}
131+
132+
/**
133+
* Builds a {@link FakeClock} instance.
134+
*
135+
* @return The built {@link FakeClock} instance.
136+
*/
137+
public FakeClock build() {
138+
return new FakeClock(/* builder= */ this);
139+
}
140+
}
141+
142+
/**
143+
* The default maximum time difference between two messages that the fake clock will automatically
144+
* advance.
145+
*/
146+
public static final long DEFAULT_MAX_AUTO_ADVANCING_TIME_DIFF_MS = 1000;
147+
58148
private static final ImmutableSet<String> UI_INTERACTION_TEST_CLASSES =
59149
ImmutableSet.of(
60150
"org.robolectric.android.internal.LocalControlledLooper",
@@ -69,6 +159,7 @@ public class FakeClock implements Clock {
69159
private final boolean isRobolectric;
70160
private final boolean isAutoAdvancing;
71161
private final Handler mainHandler;
162+
private final long maxAutoAdvancingTimeDiffMs;
72163

73164
@GuardedBy("this")
74165
private final List<HandlerMessage> handlerMessages;
@@ -129,15 +220,24 @@ public FakeClock(long initialTimeMs, boolean isAutoAdvancing) {
129220
* next message that is due to be sent.
130221
*/
131222
public FakeClock(long bootTimeMs, long initialTimeMs, boolean isAutoAdvancing) {
132-
this.bootTimeMs = bootTimeMs;
133-
this.timeSinceBootMs = initialTimeMs;
134-
this.isAutoAdvancing = isAutoAdvancing;
223+
this(
224+
new Builder()
225+
.setBootTimeMs(bootTimeMs)
226+
.setInitialTimeMs(initialTimeMs)
227+
.setIsAutoAdvancing(isAutoAdvancing));
228+
}
229+
230+
private FakeClock(Builder builder) {
231+
this.bootTimeMs = builder.bootTimeMs;
232+
this.timeSinceBootMs = builder.initialTimeMs;
233+
this.isAutoAdvancing = builder.isAutoAdvancing;
234+
this.maxAutoAdvancingTimeDiffMs = builder.maxAutoAdvancingTimeDiffMs;
135235
this.handlerMessages = new ArrayList<>();
136236
this.busyLoopers = new HashSet<>();
137237
this.mainHandler = new Handler(Looper.getMainLooper());
138238
this.isRobolectric = "robolectric".equals(Build.FINGERPRINT);
139239
if (isRobolectric) {
140-
SystemClock.setCurrentTimeMillis(initialTimeMs);
240+
SystemClock.setCurrentTimeMillis(builder.initialTimeMs);
141241
}
142242
}
143243

@@ -271,8 +371,9 @@ private synchronized void maybeTriggerMessage() {
271371
return;
272372
}
273373
if (message.timeMs > timeSinceBootMs) {
274-
if (isAutoAdvancing) {
275-
advanceTimeInternal(message.timeMs - timeSinceBootMs);
374+
long timeDiff = message.timeMs - timeSinceBootMs;
375+
if (isAutoAdvancing && timeDiff <= maxAutoAdvancingTimeDiffMs) {
376+
advanceTimeInternal(timeDiff);
276377
} else {
277378
return;
278379
}

libraries/test_utils/src/test/java/androidx/media3/test/utils/FakeClockTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,38 @@ public void createHandler_withIsAutoAdvancing_advancesTimeToNextMessages() {
321321
assertThat(clockTimes).containsExactly(0L, 20L, 50L, 70L, 100L).inOrder();
322322
}
323323

324+
@Test
325+
public void createHandler_withIsAutoAdvancing_triggersOnlyMessagesWithinDefinedMaxTimeDiff() {
326+
HandlerThread handlerThread = new HandlerThread("FakeClockTest");
327+
handlerThread.start();
328+
FakeClock fakeClock =
329+
new FakeClock.Builder()
330+
.setInitialTimeMs(0)
331+
.setIsAutoAdvancing(true)
332+
.setMaxAutoAdvancingTimeDiffMs(400)
333+
.build();
334+
HandlerWrapper handler =
335+
fakeClock.createHandler(handlerThread.getLooper(), /* callback= */ null);
336+
337+
// Post a series of immediate and delayed messages with one that is too far in the future.
338+
ArrayList<Long> clockTimes = new ArrayList<>();
339+
handler.post(
340+
() -> {
341+
handler.postDelayed(
342+
() -> clockTimes.add(fakeClock.elapsedRealtime()), /* delayMs= */ 400);
343+
handler.postDelayed(
344+
() -> clockTimes.add(fakeClock.elapsedRealtime()), /* delayMs= */ 801);
345+
handler.post(() -> clockTimes.add(fakeClock.elapsedRealtime()));
346+
handler.postDelayed(
347+
() -> clockTimes.add(fakeClock.elapsedRealtime()), /* delayMs= */ 200);
348+
});
349+
ShadowLooper.idleMainLooper();
350+
shadowOf(handler.getLooper()).idle();
351+
handlerThread.quitSafely();
352+
353+
assertThat(clockTimes).containsExactly(0L, 200L, 400L).inOrder();
354+
}
355+
324356
@Test
325357
public void createHandler_multiThreadCommunication_deliversMessagesDeterministicallyInOrder()
326358
throws Exception {

0 commit comments

Comments
 (0)