• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.N;
4 import static android.os.Build.VERSION_CODES.P;
5 import static android.os.Build.VERSION_CODES.R;
6 import static com.google.common.base.Preconditions.checkState;
7 import static org.robolectric.shadows.ShadowLooper.looperMode;
8 import static org.robolectric.util.reflector.Reflector.reflector;
9 
10 import android.os.Looper;
11 import android.view.Choreographer;
12 import android.view.Choreographer.FrameCallback;
13 import android.view.DisplayEventReceiver;
14 import java.time.Duration;
15 import org.robolectric.RuntimeEnvironment;
16 import org.robolectric.annotation.Implementation;
17 import org.robolectric.annotation.Implements;
18 import org.robolectric.annotation.LooperMode;
19 import org.robolectric.annotation.LooperMode.Mode;
20 import org.robolectric.annotation.RealObject;
21 import org.robolectric.annotation.Resetter;
22 import org.robolectric.util.PerfStatsCollector;
23 import org.robolectric.util.reflector.Accessor;
24 import org.robolectric.util.reflector.Direct;
25 import org.robolectric.util.reflector.ForType;
26 import org.robolectric.util.reflector.Static;
27 
28 /**
29  * The shadow API for {@link android.view.Choreographer}.
30  *
31  * <p>Different shadow implementations will be used depending on the current {@link LooperMode}. See
32  * {@link ShadowLegacyChoreographer} and {@link ShadowPausedChoreographer} for details.
33  */
34 @Implements(value = Choreographer.class, shadowPicker = ShadowChoreographer.Picker.class)
35 public abstract class ShadowChoreographer {
36 
37   @RealObject Choreographer realObject;
38   private ChoreographerReflector reflector;
39 
40   private static volatile boolean isPaused = false;
41   private static volatile Duration frameDelay = Duration.ofMillis(1);
42 
43   public static class Picker extends LooperShadowPicker<ShadowChoreographer> {
44 
Picker()45     public Picker() {
46       super(ShadowLegacyChoreographer.class, ShadowPausedChoreographer.class);
47     }
48   }
49 
50   /**
51    * Sets the delay between each frame. Note that the frames use the {@link ShadowSystemClock} and
52    * so have the same fidelity, when using the paused looper mode (which is the only mode supported
53    * by {@code ShadowDisplayEventReceiver}) the clock has millisecond fidelity.
54    *
55    * <p>Reasonable delays may be 15ms (approximating 60fps ~16.6ms), 10ms (approximating 90fps
56    * ~11.1ms), and 30ms (approximating 30fps ~33.3ms). Choosing too small of a frame delay may
57    * increase runtime as animation frames will have more steps.
58    *
59    * <p>Only works in {@link LooperMode.Mode#PAUSED} looper mode.
60    */
setFrameDelay(Duration delay)61   public static void setFrameDelay(Duration delay) {
62     checkState(!ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper cannot be %s", Mode.LEGACY);
63     frameDelay = delay;
64   }
65 
66   /** See {@link #setFrameDelay(Duration)}. */
getFrameDelay()67   public static Duration getFrameDelay() {
68     checkState(!ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper cannot be %s", Mode.LEGACY);
69     return frameDelay;
70   }
71 
72   /**
73    * Sets whether posting a frame should auto advance the clock or not. When paused the clock is not
74    * auto advanced, when unpaused the clock is advanced by the frame delay every time a frame
75    * callback is added. The default is not paused.
76    *
77    * <p>Only works in {@link LooperMode.Mode#PAUSED} looper mode.
78    */
setPaused(boolean paused)79   public static void setPaused(boolean paused) {
80     checkState(!ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper cannot be %s", Mode.LEGACY);
81     isPaused = paused;
82   }
83 
84   /** See {@link #setPaused(boolean)}. */
isPaused()85   public static boolean isPaused() {
86     checkState(!ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper cannot be %s", Mode.LEGACY);
87     return isPaused;
88   }
89 
90   /**
91    * Allows application to specify a fixed amount of delay when {@link #postCallback(int, Runnable,
92    * Object)} is invoked. The default delay value is 0. This can be used to avoid infinite animation
93    * tasks to be spawned when the Robolectric {@link org.robolectric.util.Scheduler} is in {@link
94    * org.robolectric.util.Scheduler.IdleState#PAUSED} mode.
95    *
96    * <p>Only supported in {@link LooperMode.Mode#LEGACY}
97    *
98    * @deprecated Use the {@link Mode#PAUSED} looper instead.
99    */
100   @Deprecated
setPostCallbackDelay(int delayMillis)101   public static void setPostCallbackDelay(int delayMillis) {
102     checkState(ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper must be %s", Mode.LEGACY);
103     ShadowLegacyChoreographer.setPostCallbackDelay(delayMillis);
104   }
105 
106   /**
107    * Allows application to specify a fixed amount of delay when {@link
108    * #postFrameCallback(FrameCallback)} is invoked. The default delay value is 0. This can be used
109    * to avoid infinite animation tasks to be spawned when in LooperMode PAUSED or {@link
110    * org.robolectric.util.Scheduler.IdleState#PAUSED} and displaying an animation.
111    *
112    * @deprecated Use the {@link Mode#PAUSED} looper and {@link #setPaused(boolean)} and {@link
113    *     #setFrameDelay(Duration)} to configure the vsync event behavior.
114    */
115   @Deprecated
setPostFrameCallbackDelay(int delayMillis)116   public static void setPostFrameCallbackDelay(int delayMillis) {
117     if (looperMode() == Mode.LEGACY) {
118       ShadowLegacyChoreographer.setPostFrameCallbackDelay(delayMillis);
119     } else {
120       setPaused(delayMillis != 0);
121       setFrameDelay(Duration.ofMillis(delayMillis == 0 ? 1 : delayMillis));
122     }
123   }
124 
125   /**
126    * Return the current inter-frame interval.
127    *
128    * <p>Can only be used in {@link LooperMode.Mode#LEGACY}
129    *
130    * @return Inter-frame interval.
131    * @deprecated Use the {@link Mode#PAUSED} looper and {@link #getFrameDelay()} to configure the
132    *     frame delay.
133    */
134   @Deprecated
getFrameInterval()135   public static long getFrameInterval() {
136     checkState(ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper must be %s", Mode.LEGACY);
137     return ShadowLegacyChoreographer.getFrameInterval();
138   }
139 
140   /**
141    * Set the inter-frame interval used to advance the clock. By default, this is set to 1ms.
142    *
143    * <p>Only supported in {@link LooperMode.Mode#LEGACY}
144    *
145    * @param frameInterval Inter-frame interval.
146    * @deprecated Use the {@link Mode#PAUSED} looper and {@link #setFrameDelay(Duration)} to
147    *     configure the frame delay.
148    */
149   @Deprecated
setFrameInterval(long frameInterval)150   public static void setFrameInterval(long frameInterval) {
151     checkState(ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper must be %s", Mode.LEGACY);
152     ShadowLegacyChoreographer.setFrameInterval(frameInterval);
153   }
154 
155   @Implementation(maxSdk = R)
doFrame(long frameTimeNanos, int frame)156   protected void doFrame(long frameTimeNanos, int frame) {
157     if (reflector == null) {
158       reflector = reflector(ChoreographerReflector.class, realObject);
159     }
160     PerfStatsCollector.getInstance()
161         .measure("doFrame", () -> reflector.doFrame(frameTimeNanos, frame));
162   }
163 
164   @Resetter
reset()165   public static void reset() {
166     isPaused = false;
167     frameDelay = Duration.ofMillis(1);
168     if (RuntimeEnvironment.getApiLevel() >= N) {
169       ShadowBackdropFrameRenderer.reset();
170     }
171     if (RuntimeEnvironment.getApiLevel() >= P) {
172       reflector(ChoreographerReflector.class).setMainInstance(null);
173     }
174   }
175 
176   /** Accessor interface for {@link Choreographer}'s internals */
177   @ForType(Choreographer.class)
178   protected interface ChoreographerReflector {
179     @Accessor("sThreadInstance")
180     @Static
getThreadInstance()181     ThreadLocal<Choreographer> getThreadInstance();
182 
183     // used to reset main instance
184     @Accessor("mMainInstance")
185     @Static
setMainInstance(Choreographer choreographer)186     void setMainInstance(Choreographer choreographer);
187 
188     @Direct
doFrame(long frameTimeNanos, int frame)189     void doFrame(long frameTimeNanos, int frame);
190 
191     @Accessor("mDisplayEventReceiver")
getReceiver()192     DisplayEventReceiver getReceiver();
193 
194     @Direct
__constructor__(Looper looper)195     void __constructor__(Looper looper);
196 
197     @Direct
__constructor__(Looper looper, int vsyncSource, long layerHandle)198     void __constructor__(Looper looper, int vsyncSource, long layerHandle);
199 
200     @Direct
__constructor__(Looper looper, int vsyncSource)201     void __constructor__(Looper looper, int vsyncSource);
202   }
203 }
204