Commit c6529344 by tonihei Committed by Oliver Woodman

Introduce Handler interface allowing to switch to other Handlers.

Especially this removes the need for the Clock interface to directly
implement Handler methods. Instead, we have a separate Handler interface
and the FakeClock is able to construct such a Handler.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=179918255
parent 35d4cbf9
......@@ -15,13 +15,12 @@
*/
package com.google.android.exoplayer2.util;
import android.os.Handler;
/**
* An interface through which system clocks can be read. The {@link #DEFAULT} implementation
* must be used for all non-test cases.
* An interface through which system clocks can be read. The {@link #DEFAULT} implementation must be
* used for all non-test cases. Implementations must also be able to create a {@link HandlerWrapper}
* which uses the underlying clock to schedule delayed messages.
*/
public interface Clock {
public interface Clock extends HandlerWrapper.Factory {
/**
* Default {@link Clock} to use for all non-test cases.
......@@ -37,15 +36,4 @@ public interface Clock {
* @see android.os.SystemClock#sleep(long)
*/
void sleep(long sleepTimeMs);
/**
* Post a {@link Runnable} on a {@link Handler} thread with a delay measured by this clock.
* @see Handler#postDelayed(Runnable, long)
*
* @param handler The {@link Handler} to post the {@code runnable} on.
* @param runnable A {@link Runnable} to be posted.
* @param delayMs The delay in milliseconds as measured by this clock.
*/
void postDelayed(Handler handler, Runnable runnable, long delayMs);
}
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.util;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.Nullable;
/**
* An interface to call through to an {@link Handler}. The {@link Factory#DEFAULT} factory must be
* used for all non-test cases.
*/
public interface HandlerWrapper {
/** A factory for handler instances. */
interface Factory {
/** Default HandlerWrapper factory to use for all non-test cases. */
Factory DEFAULT = new SystemHandler.Factory();
/**
* Creates a HandlerWrapper running a specified looper and using a specified callback for
* messages.
*
* @see Handler#Handler(Looper, Handler.Callback).
*/
HandlerWrapper createHandler(Looper looper, @Nullable Handler.Callback callback);
}
/** @see Handler#getLooper(). */
Looper getLooper();
/** @see Handler#obtainMessage(int). */
Message obtainMessage(int what);
/** @see Handler#obtainMessage(int, Object). */
Message obtainMessage(int what, Object obj);
/** @see Handler#obtainMessage(int, int, int). */
Message obtainMessage(int what, int arg1, int arg2);
/** @see Handler#obtainMessage(int, int, int, Object). */
Message obtainMessage(int what, int arg1, int arg2, Object obj);
/** @see Handler#sendEmptyMessage(int). */
boolean sendEmptyMessage(int what);
/** @see Handler#sendEmptyMessageDelayed(int, long). */
boolean sendEmptyMessageDelayed(int what, long delayMs);
/** @see Handler#removeMessages(int). */
void removeMessages(int what);
/** @see Handler#removeCallbacksAndMessages(Object). */
void removeCallbacksAndMessages(Object token);
/** @see Handler#post(Runnable). */
boolean post(Runnable runnable);
/** @see Handler#postDelayed(Runnable, long). */
boolean postDelayed(Runnable runnable, long delayMs);
}
......@@ -15,7 +15,9 @@
*/
package com.google.android.exoplayer2.util;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Looper;
import android.support.annotation.Nullable;
/**
* The standard implementation of {@link Clock}.
......@@ -33,8 +35,7 @@ import android.os.Handler;
}
@Override
public void postDelayed(Handler handler, Runnable runnable, long delayMs) {
handler.postDelayed(runnable, delayMs);
public HandlerWrapper createHandler(Looper looper, @Nullable Callback callback) {
return HandlerWrapper.Factory.DEFAULT.createHandler(looper, callback);
}
}
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.util;
import android.os.Handler.Callback;
import android.os.Looper;
import android.os.Message;
/** The standard implementation of {@link HandlerWrapper}. */
/* package */ final class SystemHandler implements HandlerWrapper {
/* package */ static final class Factory implements HandlerWrapper.Factory {
@Override
public HandlerWrapper createHandler(Looper looper, Callback callback) {
return new SystemHandler(new android.os.Handler(looper, callback));
}
}
private final android.os.Handler handler;
private SystemHandler(android.os.Handler handler) {
this.handler = handler;
}
@Override
public Looper getLooper() {
return handler.getLooper();
}
@Override
public Message obtainMessage(int what) {
return handler.obtainMessage(what);
}
@Override
public Message obtainMessage(int what, Object obj) {
return handler.obtainMessage(what, obj);
}
@Override
public Message obtainMessage(int what, int arg1, int arg2) {
return handler.obtainMessage(what, arg1, arg2);
}
@Override
public Message obtainMessage(int what, int arg1, int arg2, Object obj) {
return handler.obtainMessage(what, arg1, arg2, obj);
}
@Override
public boolean sendEmptyMessage(int what) {
return handler.sendEmptyMessage(what);
}
@Override
public boolean sendEmptyMessageDelayed(int what, long delayMs) {
return handler.sendEmptyMessageDelayed(what, delayMs);
}
@Override
public void removeMessages(int what) {
handler.removeMessages(what);
}
@Override
public void removeCallbacksAndMessages(Object token) {
handler.removeCallbacksAndMessages(token);
}
@Override
public boolean post(Runnable runnable) {
return handler.post(runnable);
}
@Override
public boolean postDelayed(Runnable runnable, long delayMs) {
return handler.postDelayed(runnable, delayMs);
}
}
......@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.testutil;
import android.os.Handler;
import android.util.Log;
import android.view.Surface;
import com.google.android.exoplayer2.C;
......@@ -31,6 +30,7 @@ import com.google.android.exoplayer2.testutil.ActionSchedule.ActionNode;
import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable;
import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.util.HandlerWrapper;
/**
* Base class for actions to perform during playback tests.
......@@ -58,15 +58,19 @@ public abstract class Action {
* @param handler The handler to use to pass to the next action.
* @param nextAction The next action to schedule immediately after this action finished.
*/
public final void doActionAndScheduleNext(SimpleExoPlayer player,
MappingTrackSelector trackSelector, Surface surface, Handler handler, ActionNode nextAction) {
public final void doActionAndScheduleNext(
SimpleExoPlayer player,
MappingTrackSelector trackSelector,
Surface surface,
HandlerWrapper handler,
ActionNode nextAction) {
Log.i(tag, description);
doActionAndScheduleNextImpl(player, trackSelector, surface, handler, nextAction);
}
/**
* Called by {@link #doActionAndScheduleNext(SimpleExoPlayer, MappingTrackSelector, Surface,
* Handler, ActionNode)} to perform the action and to schedule the next action node.
* HandlerWrapper, ActionNode)} to perform the action and to schedule the next action node.
*
* @param player The player to which the action should be applied.
* @param trackSelector The track selector to which the action should be applied.
......@@ -74,8 +78,12 @@ public abstract class Action {
* @param handler The handler to use to pass to the next action.
* @param nextAction The next action to schedule immediately after this action finished.
*/
protected void doActionAndScheduleNextImpl(SimpleExoPlayer player,
MappingTrackSelector trackSelector, Surface surface, Handler handler, ActionNode nextAction) {
protected void doActionAndScheduleNextImpl(
SimpleExoPlayer player,
MappingTrackSelector trackSelector,
Surface surface,
HandlerWrapper handler,
ActionNode nextAction) {
doActionImpl(player, trackSelector, surface);
if (nextAction != null) {
nextAction.schedule(player, trackSelector, surface, handler);
......@@ -84,14 +92,14 @@ public abstract class Action {
/**
* Called by {@link #doActionAndScheduleNextImpl(SimpleExoPlayer, MappingTrackSelector, Surface,
* Handler, ActionNode)} to perform the action.
* HandlerWrapper, ActionNode)} to perform the action.
*
* @param player The player to which the action should be applied.
* @param trackSelector The track selector to which the action should be applied.
* @param surface The surface to use when applying actions.
*/
protected abstract void doActionImpl(SimpleExoPlayer player, MappingTrackSelector trackSelector,
Surface surface);
protected abstract void doActionImpl(
SimpleExoPlayer player, MappingTrackSelector trackSelector, Surface surface);
/**
* Calls {@link Player#seekTo(long)} or {@link Player#seekTo(int, long)}.
......@@ -403,7 +411,7 @@ public abstract class Action {
} else {
message.setPosition(positionMs);
}
message.setHandler(new Handler());
message.setHandler(new android.os.Handler());
message.setDeleteAfterDelivery(deleteAfterDelivery);
message.send();
}
......@@ -449,8 +457,11 @@ public abstract class Action {
}
@Override
protected void doActionAndScheduleNextImpl(final SimpleExoPlayer player,
final MappingTrackSelector trackSelector, final Surface surface, final Handler handler,
protected void doActionAndScheduleNextImpl(
final SimpleExoPlayer player,
final MappingTrackSelector trackSelector,
final Surface surface,
final HandlerWrapper handler,
final ActionNode nextAction) {
if (nextAction == null) {
return;
......@@ -493,8 +504,11 @@ public abstract class Action {
}
@Override
protected void doActionAndScheduleNextImpl(final SimpleExoPlayer player,
final MappingTrackSelector trackSelector, final Surface surface, final Handler handler,
protected void doActionAndScheduleNextImpl(
final SimpleExoPlayer player,
final MappingTrackSelector trackSelector,
final Surface surface,
final HandlerWrapper handler,
final ActionNode nextAction) {
if (nextAction == null) {
return;
......@@ -533,8 +547,11 @@ public abstract class Action {
}
@Override
protected void doActionAndScheduleNextImpl(final SimpleExoPlayer player,
final MappingTrackSelector trackSelector, final Surface surface, final Handler handler,
protected void doActionAndScheduleNextImpl(
final SimpleExoPlayer player,
final MappingTrackSelector trackSelector,
final Surface surface,
final HandlerWrapper handler,
final ActionNode nextAction) {
if (nextAction == null) {
return;
......@@ -575,8 +592,11 @@ public abstract class Action {
}
@Override
protected void doActionAndScheduleNextImpl(final SimpleExoPlayer player,
final MappingTrackSelector trackSelector, final Surface surface, final Handler handler,
protected void doActionAndScheduleNextImpl(
final SimpleExoPlayer player,
final MappingTrackSelector trackSelector,
final Surface surface,
final HandlerWrapper handler,
final ActionNode nextAction) {
if (nextAction == null) {
return;
......
......@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.testutil;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.view.Surface;
......@@ -46,7 +45,7 @@ import com.google.android.exoplayer2.testutil.Action.WaitForSeekProcessed;
import com.google.android.exoplayer2.testutil.Action.WaitForTimelineChanged;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.HandlerWrapper;
/**
* Schedules a sequence of {@link Action}s for execution during a test.
......@@ -87,8 +86,12 @@ public final class ActionSchedule {
* @param callback A {@link Callback} to notify when the action schedule finishes, or null if no
* notification is needed.
*/
/* package */ void start(SimpleExoPlayer player, MappingTrackSelector trackSelector,
Surface surface, Handler mainHandler, @Nullable Callback callback) {
/* package */ void start(
SimpleExoPlayer player,
MappingTrackSelector trackSelector,
Surface surface,
HandlerWrapper mainHandler,
@Nullable Callback callback) {
callbackAction.setCallback(callback);
rootNode.schedule(player, trackSelector, surface, mainHandler);
}
......@@ -99,7 +102,6 @@ public final class ActionSchedule {
public static final class Builder {
private final String tag;
private final Clock clock;
private final ActionNode rootNode;
private long currentDelayMs;
......@@ -109,17 +111,8 @@ public final class ActionSchedule {
* @param tag A tag to use for logging.
*/
public Builder(String tag) {
this(tag, Clock.DEFAULT);
}
/**
* @param tag A tag to use for logging.
* @param clock A clock to use for measuring delays.
*/
public Builder(String tag, Clock clock) {
this.tag = tag;
this.clock = clock;
rootNode = new ActionNode(new RootAction(tag), clock, 0);
rootNode = new ActionNode(new RootAction(tag), 0);
previousNode = rootNode;
}
......@@ -141,7 +134,7 @@ public final class ActionSchedule {
* @return The builder, for convenience.
*/
public Builder apply(Action action) {
return appendActionNode(new ActionNode(action, clock, currentDelayMs));
return appendActionNode(new ActionNode(action, currentDelayMs));
}
/**
......@@ -152,7 +145,7 @@ public final class ActionSchedule {
* @return The builder, for convenience.
*/
public Builder repeat(Action action, long intervalMs) {
return appendActionNode(new ActionNode(action, clock, currentDelayMs, intervalMs));
return appendActionNode(new ActionNode(action, currentDelayMs, intervalMs));
}
/**
......@@ -459,7 +452,6 @@ public final class ActionSchedule {
/* package */ static final class ActionNode implements Runnable {
private final Action action;
private final Clock clock;
private final long delayMs;
private final long repeatIntervalMs;
......@@ -468,27 +460,24 @@ public final class ActionSchedule {
private SimpleExoPlayer player;
private MappingTrackSelector trackSelector;
private Surface surface;
private Handler mainHandler;
private HandlerWrapper mainHandler;
/**
* @param action The wrapped action.
* @param clock The clock to use for measuring the delay.
* @param delayMs The delay between the node being scheduled and the action being executed.
*/
public ActionNode(Action action, Clock clock, long delayMs) {
this(action, clock, delayMs, C.TIME_UNSET);
public ActionNode(Action action, long delayMs) {
this(action, delayMs, C.TIME_UNSET);
}
/**
* @param action The wrapped action.
* @param clock The clock to use for measuring the delay.
* @param delayMs The delay between the node being scheduled and the action being executed.
* @param repeatIntervalMs The interval between one execution and the next repetition. If set to
* {@link C#TIME_UNSET}, the action is executed once only.
*/
public ActionNode(Action action, Clock clock, long delayMs, long repeatIntervalMs) {
public ActionNode(Action action, long delayMs, long repeatIntervalMs) {
this.action = action;
this.clock = clock;
this.delayMs = delayMs;
this.repeatIntervalMs = repeatIntervalMs;
}
......@@ -503,16 +492,19 @@ public final class ActionSchedule {
}
/**
* Schedules {@link #action} to be executed after {@link #delayMs}. The {@link #next} node
* will be scheduled immediately after {@link #action} is executed.
* Schedules {@link #action} to be executed after {@link #delayMs}. The {@link #next} node will
* be scheduled immediately after {@link #action} is executed.
*
* @param player The player to which actions should be applied.
* @param trackSelector The track selector to which actions should be applied.
* @param surface The surface to use when applying actions.
* @param mainHandler A handler associated with the main thread of the host activity.
*/
public void schedule(SimpleExoPlayer player, MappingTrackSelector trackSelector,
Surface surface, Handler mainHandler) {
public void schedule(
SimpleExoPlayer player,
MappingTrackSelector trackSelector,
Surface surface,
HandlerWrapper mainHandler) {
this.player = player;
this.trackSelector = trackSelector;
this.surface = surface;
......@@ -520,7 +512,7 @@ public final class ActionSchedule {
if (delayMs == 0 && Looper.myLooper() == mainHandler.getLooper()) {
run();
} else {
clock.postDelayed(mainHandler, this, delayMs);
mainHandler.postDelayed(this, delayMs);
}
}
......@@ -528,13 +520,15 @@ public final class ActionSchedule {
public void run() {
action.doActionAndScheduleNext(player, trackSelector, surface, mainHandler, next);
if (repeatIntervalMs != C.TIME_UNSET) {
clock.postDelayed(mainHandler, new Runnable() {
@Override
public void run() {
action.doActionAndScheduleNext(player, trackSelector, surface, mainHandler, null);
clock.postDelayed(mainHandler, this, repeatIntervalMs);
}
}, repeatIntervalMs);
mainHandler.postDelayed(
new Runnable() {
@Override
public void run() {
action.doActionAndScheduleNext(player, trackSelector, surface, mainHandler, null);
mainHandler.postDelayed(this, repeatIntervalMs);
}
},
repeatIntervalMs);
}
}
......@@ -577,7 +571,7 @@ public final class ActionSchedule {
SimpleExoPlayer player,
MappingTrackSelector trackSelector,
Surface surface,
Handler handler,
HandlerWrapper handler,
ActionNode nextAction) {
Assertions.checkArgument(nextAction == null);
if (callback != null) {
......
......@@ -16,7 +16,7 @@
package com.google.android.exoplayer2.testutil;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.Surface;
......@@ -42,6 +42,7 @@ import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.HandlerWrapper;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import junit.framework.Assert;
......@@ -72,7 +73,7 @@ public abstract class ExoHostedTest extends Player.DefaultEventListener implemen
private final ConditionVariable testFinished;
private ActionSchedule pendingSchedule;
private Handler actionHandler;
private HandlerWrapper actionHandler;
private MappingTrackSelector trackSelector;
private SimpleExoPlayer player;
private Surface surface;
......@@ -187,7 +188,8 @@ public abstract class ExoHostedTest extends Player.DefaultEventListener implemen
player.addAudioDebugListener(this);
player.addVideoDebugListener(this);
player.setPlayWhenReady(true);
actionHandler = new Handler();
actionHandler =
HandlerWrapper.Factory.DEFAULT.createHandler(Looper.myLooper(), /* callback= */ null);
// Schedule any pending actions.
if (pendingSchedule != null) {
pendingSchedule.start(player, trackSelector, surface, actionHandler, /* callback= */ null);
......
......@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.testutil;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.DefaultLoadControl;
......@@ -38,6 +37,8 @@ import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.HandlerWrapper;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.util.ArrayList;
......@@ -91,6 +92,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
public static final Format AUDIO_FORMAT = Format.createAudioSampleFormat(null,
MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null);
private Clock clock;
private PlayerFactory playerFactory;
private Timeline timeline;
private Object manifest;
......@@ -237,6 +239,18 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
}
/**
* Sets the {@link Clock} to be used by the test runner. The default value is {@link
* Clock#DEFAULT}.
*
* @param clock A {@link Clock} to be used by the test runner.
* @return This builder.
*/
public Builder setClock(Clock clock) {
this.clock = clock;
return this;
}
/**
* Sets an {@link ActionSchedule} to be run by the test runner. The first action will be
* executed immediately before {@link SimpleExoPlayer#prepare(MediaSource)}.
*
......@@ -312,19 +326,25 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
if (renderers == null) {
renderers = new Renderer[] {new FakeRenderer(supportedFormats)};
}
renderersFactory = new RenderersFactory() {
@Override
public Renderer[] createRenderers(Handler eventHandler,
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener, TextOutput textRendererOutput,
MetadataOutput metadataRendererOutput) {
return renderers;
}
};
renderersFactory =
new RenderersFactory() {
@Override
public Renderer[] createRenderers(
android.os.Handler eventHandler,
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener,
TextOutput textRendererOutput,
MetadataOutput metadataRendererOutput) {
return renderers;
}
};
}
if (loadControl == null) {
loadControl = new DefaultLoadControl();
}
if (clock == null) {
clock = Clock.DEFAULT;
}
if (playerFactory == null) {
playerFactory = new PlayerFactory() {
@Override
......@@ -344,6 +364,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
expectedPlayerEndedCount = 1;
}
return new ExoPlayerTestRunner(
clock,
playerFactory,
mediaSource,
renderersFactory,
......@@ -368,7 +389,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
private final @Nullable AudioRendererEventListener audioRendererEventListener;
private final HandlerThread playerThread;
private final Handler handler;
private final HandlerWrapper handler;
private final CountDownLatch endedCountDownLatch;
private final CountDownLatch actionScheduleFinishedCountDownLatch;
private final ArrayList<Timeline> timelines;
......@@ -383,6 +404,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
private boolean playerWasPrepared;
private ExoPlayerTestRunner(
Clock clock,
PlayerFactory playerFactory,
MediaSource mediaSource,
RenderersFactory renderersFactory,
......@@ -411,7 +433,7 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
this.actionScheduleFinishedCountDownLatch = new CountDownLatch(actionSchedule != null ? 1 : 0);
this.playerThread = new HandlerThread("ExoPlayerTest thread");
playerThread.start();
this.handler = new Handler(playerThread.getLooper());
this.handler = clock.createHandler(playerThread.getLooper(), /* callback= */ null);
}
// Called on the test thread to run the test.
......
......@@ -15,19 +15,21 @@
*/
package com.google.android.exoplayer2.testutil;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Looper;
import android.os.Message;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.HandlerWrapper;
import java.util.ArrayList;
import java.util.List;
/**
* Fake {@link Clock} implementation independent of {@link android.os.SystemClock}.
*/
/** Fake {@link Clock} implementation independent of {@link android.os.SystemClock}. */
public final class FakeClock implements Clock {
private long currentTimeMs;
private final List<Long> wakeUpTimes;
private final List<HandlerPostData> handlerPosts;
private final List<HandlerMessageData> handlerMessages;
private long currentTimeMs;
/**
* Create {@link FakeClock} with an arbitrary initial timestamp.
......@@ -37,7 +39,7 @@ public final class FakeClock implements Clock {
public FakeClock(long initialTimeMs) {
this.currentTimeMs = initialTimeMs;
this.wakeUpTimes = new ArrayList<>();
this.handlerPosts = new ArrayList<>();
this.handlerMessages = new ArrayList<>();
}
/**
......@@ -53,10 +55,9 @@ public final class FakeClock implements Clock {
break;
}
}
for (int i = handlerPosts.size() - 1; i >= 0; i--) {
if (handlerPosts.get(i).postTime <= currentTimeMs) {
HandlerPostData postData = handlerPosts.remove(i);
postData.handler.post(postData.runnable);
for (int i = handlerMessages.size() - 1; i >= 0; i--) {
if (handlerMessages.get(i).maybeSendToTarget(currentTimeMs)) {
handlerMessages.remove(i);
}
}
}
......@@ -84,27 +85,131 @@ public final class FakeClock implements Clock {
}
@Override
public synchronized void postDelayed(Handler handler, Runnable runnable, long delayMs) {
if (delayMs <= 0) {
handler.post(runnable);
} else {
handlerPosts.add(new HandlerPostData(currentTimeMs + delayMs, handler, runnable));
}
public HandlerWrapper createHandler(Looper looper, Callback callback) {
return new ClockHandler(looper, callback);
}
private static final class HandlerPostData {
/** Adds a handler post to list of pending messages. */
protected synchronized void addDelayedHandlerMessage(
HandlerWrapper handler, Runnable runnable, long delayMs) {
handlerMessages.add(new HandlerMessageData(currentTimeMs + delayMs, handler, runnable));
}
public final long postTime;
public final Handler handler;
public final Runnable runnable;
/** Adds an empty handler message to list of pending messages. */
protected synchronized void addDelayedHandlerMessage(
HandlerWrapper handler, int message, long delayMs) {
handlerMessages.add(new HandlerMessageData(currentTimeMs + delayMs, handler, message));
}
/** Message data saved to send messages or execute runnables at a later time on a Handler. */
private static final class HandlerMessageData {
private final long postTime;
private final HandlerWrapper handler;
private final Runnable runnable;
private final int message;
public HandlerPostData(long postTime, Handler handler, Runnable runnable) {
public HandlerMessageData(long postTime, HandlerWrapper handler, Runnable runnable) {
this.postTime = postTime;
this.handler = handler;
this.runnable = runnable;
this.message = 0;
}
public HandlerMessageData(long postTime, HandlerWrapper handler, int message) {
this.postTime = postTime;
this.handler = handler;
this.runnable = null;
this.message = message;
}
/** Sends the message and returns whether the message was sent to its target. */
public boolean maybeSendToTarget(long currentTimeMs) {
if (postTime <= currentTimeMs) {
if (runnable != null) {
handler.post(runnable);
} else {
handler.sendEmptyMessage(message);
}
return true;
}
return false;
}
}
/** HandlerWrapper implementation using the enclosing Clock to schedule delayed messages. */
private final class ClockHandler implements HandlerWrapper {
private final android.os.Handler handler;
public ClockHandler(Looper looper, Callback callback) {
handler = new android.os.Handler(looper, callback);
}
@Override
public Looper getLooper() {
return handler.getLooper();
}
@Override
public Message obtainMessage(int what) {
return handler.obtainMessage(what);
}
@Override
public Message obtainMessage(int what, Object obj) {
return handler.obtainMessage(what, obj);
}
@Override
public Message obtainMessage(int what, int arg1, int arg2) {
return handler.obtainMessage(what, arg1, arg2);
}
@Override
public Message obtainMessage(int what, int arg1, int arg2, Object obj) {
return handler.obtainMessage(what, arg1, arg2, obj);
}
@Override
public boolean sendEmptyMessage(int what) {
return handler.sendEmptyMessage(what);
}
@Override
public boolean sendEmptyMessageDelayed(int what, long delayMs) {
if (delayMs <= 0) {
return handler.sendEmptyMessage(what);
} else {
addDelayedHandlerMessage(this, what, delayMs);
return true;
}
}
@Override
public void removeMessages(int what) {
handler.removeMessages(what);
}
@Override
public void removeCallbacksAndMessages(Object token) {
handler.removeCallbacksAndMessages(token);
}
@Override
public boolean post(Runnable runnable) {
return handler.post(runnable);
}
@Override
public boolean postDelayed(Runnable runnable, long delayMs) {
if (delayMs <= 0) {
return handler.post(runnable);
} else {
addDelayedHandlerMessage(this, runnable, delayMs);
return true;
}
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment