diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 7bc9fadf42..d4d7b4ae60 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -35,6 +35,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; @@ -138,6 +139,7 @@ public final class CastPlayer extends BasePlayer { listeners = new ListenerSet<>( Looper.getMainLooper(), + Clock.DEFAULT, Player.Events::new, (listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags)); diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java index a5802fad0d..6b62af93f3 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.testutil.StubExoPlayer; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ListenerSet; /** A fake player for testing content/ad playback. */ @@ -43,6 +44,7 @@ import com.google.android.exoplayer2.util.ListenerSet; listeners = new ListenerSet<>( Looper.getMainLooper(), + Clock.DEFAULT, Player.Events::new, (listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags)); period = new Timeline.Period(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index d8e6affb2b..7c8f9addbb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -154,6 +154,7 @@ import java.util.List; listeners = new ListenerSet<>( applicationLooper, + clock, Player.Events::new, (listener, eventFlags) -> listener.onEvents(playerForListeners, eventFlags)); mediaSourceHolderSnapshots = new ArrayList<>(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index e4705bd761..20bb920c57 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -91,6 +91,7 @@ public class AnalyticsCollector listeners = new ListenerSet<>( Util.getCurrentOrMainLooper(), + clock, AnalyticsListener.Events::new, (listener, eventFlags) -> {}); period = new Period(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java b/library/core/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java index 8343d27f42..edf775bd5b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/HandlerWrapper.java @@ -26,39 +26,42 @@ import androidx.annotation.Nullable; */ public interface HandlerWrapper { - /** @see Handler#getLooper() */ + /** See {@link Handler#getLooper()}. */ Looper getLooper(); - /** @see Handler#obtainMessage(int) */ + /** See {@link Handler#hasMessages(int)}. */ + boolean hasMessages(int what); + + /** See {@link Handler#obtainMessage(int)}. */ Message obtainMessage(int what); - /** @see Handler#obtainMessage(int, Object) */ + /** See {@link Handler#obtainMessage(int, Object)}. */ Message obtainMessage(int what, @Nullable Object obj); - /** @see Handler#obtainMessage(int, int, int) */ + /** See {@link Handler#obtainMessage(int, int, int)}. */ Message obtainMessage(int what, int arg1, int arg2); - /** @see Handler#obtainMessage(int, int, int, Object) */ + /** See {@link Handler#obtainMessage(int, int, int, Object)}. */ Message obtainMessage(int what, int arg1, int arg2, @Nullable Object obj); - /** @see Handler#sendEmptyMessage(int) */ + /** See {@link Handler#sendEmptyMessage(int)}. */ boolean sendEmptyMessage(int what); - /** @see Handler#sendEmptyMessageDelayed(int, long) */ + /** See {@link Handler#sendEmptyMessageDelayed(int, long)}. */ boolean sendEmptyMessageDelayed(int what, int delayMs); - /** @see Handler#sendEmptyMessageAtTime(int, long) */ + /** See {@link Handler#sendEmptyMessageAtTime(int, long)}. */ boolean sendEmptyMessageAtTime(int what, long uptimeMs); - /** @see Handler#removeMessages(int) */ + /** See {@link Handler#removeMessages(int)}. */ void removeMessages(int what); - /** @see Handler#removeCallbacksAndMessages(Object) */ + /** See {@link Handler#removeCallbacksAndMessages(Object)}. */ void removeCallbacksAndMessages(@Nullable Object token); - /** @see Handler#post(Runnable) */ + /** See {@link Handler#post(Runnable)}. */ boolean post(Runnable runnable); - /** @see Handler#postDelayed(Runnable, long) */ + /** See {@link Handler#postDelayed(Runnable, long)}. */ boolean postDelayed(Runnable runnable, long delayMs); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java index d0df7a662e..a9a749e47f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ListenerSet.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.util; -import android.os.Handler; import android.os.Looper; import android.os.Message; import androidx.annotation.CheckResult; @@ -72,7 +71,8 @@ public final class ListenerSet { private static final int MSG_ITERATION_FINISHED = 0; private static final int MSG_LAZY_RELEASE = 1; - private final Handler handler; + private final Clock clock; + private final HandlerWrapper handler; private final Supplier eventFlagsSupplier; private final IterationFinishedEvent iterationFinishedEvent; private final CopyOnWriteArraySet> listeners; @@ -86,6 +86,7 @@ public final class ListenerSet { * * @param looper A {@link Looper} used to call listeners on. The same {@link Looper} must be used * to call all other methods of this class. + * @param clock A {@link Clock}. * @param eventFlagsSupplier A {@link Supplier} for new instances of {@link E the event flags * type}. * @param iterationFinishedEvent An {@link IterationFinishedEvent} sent when all other events sent @@ -93,11 +94,13 @@ public final class ListenerSet { */ public ListenerSet( Looper looper, + Clock clock, Supplier eventFlagsSupplier, IterationFinishedEvent iterationFinishedEvent) { this( /* listeners= */ new CopyOnWriteArraySet<>(), looper, + clock, eventFlagsSupplier, iterationFinishedEvent); } @@ -105,8 +108,10 @@ public final class ListenerSet { private ListenerSet( CopyOnWriteArraySet> listeners, Looper looper, + Clock clock, Supplier eventFlagsSupplier, IterationFinishedEvent iterationFinishedEvent) { + this.clock = clock; this.listeners = listeners; this.eventFlagsSupplier = eventFlagsSupplier; this.iterationFinishedEvent = iterationFinishedEvent; @@ -114,7 +119,7 @@ public final class ListenerSet { queuedEvents = new ArrayDeque<>(); // It's safe to use "this" because we don't send a message before exiting the constructor. @SuppressWarnings("methodref.receiver.bound.invalid") - Handler handler = Util.createHandler(looper, this::handleMessage); + HandlerWrapper handler = clock.createHandler(looper, this::handleMessage); this.handler = handler; } @@ -129,7 +134,7 @@ public final class ListenerSet { @CheckResult public ListenerSet copy( Looper looper, IterationFinishedEvent iterationFinishedEvent) { - return new ListenerSet<>(listeners, looper, eventFlagsSupplier, iterationFinishedEvent); + return new ListenerSet<>(listeners, looper, clock, eventFlagsSupplier, iterationFinishedEvent); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/SystemHandlerWrapper.java b/library/core/src/main/java/com/google/android/exoplayer2/util/SystemHandlerWrapper.java index dc0f93165a..7b504f0779 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/SystemHandlerWrapper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/SystemHandlerWrapper.java @@ -33,6 +33,11 @@ import androidx.annotation.Nullable; return handler.getLooper(); } + @Override + public boolean hasMessages(int what) { + return handler.hasMessages(what); + } + @Override public Message obtainMessage(int what) { return handler.obtainMessage(what); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/ListenerSetTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/ListenerSetTest.java index 6526b4db22..7d0b77664d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/ListenerSetTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/ListenerSetTest.java @@ -43,7 +43,8 @@ public class ListenerSetTest { @Test public void queueEvent_withoutFlush_sendsNoEvents() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); TestListener listener = mock(TestListener.class); listenerSet.add(listener); @@ -57,7 +58,8 @@ public class ListenerSetTest { @Test public void flushEvents_sendsPreviouslyQueuedEventsToAllListeners() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); TestListener listener1 = mock(TestListener.class); TestListener listener2 = mock(TestListener.class); listenerSet.add(listener1); @@ -81,7 +83,8 @@ public class ListenerSetTest { @Test public void flushEvents_recursive_sendsEventsInCorrectOrder() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); // Listener1 sends callback3 recursively when receiving callback1. TestListener listener1 = spy( @@ -114,7 +117,8 @@ public class ListenerSetTest { public void flushEvents_withMultipleMessageQueueIterations_sendsIterationFinishedEventPerIteration() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); // Listener1 sends callback1 recursively when receiving callback3. TestListener listener1 = spy( @@ -170,7 +174,8 @@ public class ListenerSetTest { @Test public void flushEvents_calledFromIterationFinishedCallback_restartsIterationFinishedEvents() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); // Listener2 sends callback1 recursively when receiving the iteration finished event. TestListener listener2 = spy( @@ -212,7 +217,8 @@ public class ListenerSetTest { @Test public void flushEvents_withUnsetEventFlag_doesNotThrow() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); listenerSet.queueEvent(/* eventFlag= */ C.INDEX_UNSET, TestListener::callback1); listenerSet.flushEvents(); @@ -224,7 +230,8 @@ public class ListenerSetTest { @Test public void add_withRecursion_onlyReceivesUpdatesForFutureEvents() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); TestListener listener2 = mock(TestListener.class); // Listener1 adds listener2 recursively. TestListener listener1 = @@ -256,7 +263,8 @@ public class ListenerSetTest { @Test public void add_withQueueing_onlyReceivesUpdatesForFutureEvents() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); TestListener listener1 = mock(TestListener.class); TestListener listener2 = mock(TestListener.class); @@ -281,7 +289,8 @@ public class ListenerSetTest { @Test public void remove_withRecursion_stopsReceivingEventsImmediately() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); TestListener listener2 = mock(TestListener.class); // Listener1 removes listener2 recursively. TestListener listener1 = @@ -309,7 +318,8 @@ public class ListenerSetTest { @Test public void remove_withQueueing_stopsReceivingEventsImmediately() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); TestListener listener1 = mock(TestListener.class); TestListener listener2 = mock(TestListener.class); listenerSet.add(listener1); @@ -330,7 +340,8 @@ public class ListenerSetTest { @Test public void release_stopsForwardingEventsImmediately() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); TestListener listener2 = mock(TestListener.class); // Listener1 releases the set from within the callback. TestListener listener1 = @@ -357,7 +368,8 @@ public class ListenerSetTest { @Test public void release_preventsRegisteringNewListeners() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); TestListener listener = mock(TestListener.class); listenerSet.release(); @@ -370,7 +382,8 @@ public class ListenerSetTest { @Test public void lazyRelease_stopsForwardingEventsFromNewHandlerMessagesAndCallsReleaseCallback() { ListenerSet listenerSet = - new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished); + new ListenerSet<>( + Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished); TestListener listener = mock(TestListener.class); listenerSet.add(listener); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java index 64d6ceb0e2..9e90af4d83 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java @@ -148,6 +148,16 @@ public class FakeClock implements Clock { return true; } + private synchronized boolean hasPendingMessage(ClockHandler handler, int what) { + for (int i = 0; i < handlerMessages.size(); i++) { + HandlerMessageData message = handlerMessages.get(i); + if (message.handler.equals(handler) && message.message == what) { + return true; + } + } + return handler.handler.hasMessages(what); + } + /** Message data saved to send messages or execute runnables at a later time on a Handler. */ private static final class HandlerMessageData { @@ -198,6 +208,11 @@ public class FakeClock implements Clock { return handler.getLooper(); } + @Override + public boolean hasMessages(int what) { + return hasPendingMessage(/* handler= */ this, what); + } + @Override public Message obtainMessage(int what) { return handler.obtainMessage(what);