• 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.R;
5 import static android.os.Build.VERSION_CODES.S;
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.ClassName;
17 import org.robolectric.annotation.Implementation;
18 import org.robolectric.annotation.Implements;
19 import org.robolectric.annotation.LooperMode;
20 import org.robolectric.annotation.LooperMode.Mode;
21 import org.robolectric.annotation.RealObject;
22 import org.robolectric.annotation.Resetter;
23 import org.robolectric.util.PerfStatsCollector;
24 import org.robolectric.util.reflector.Accessor;
25 import org.robolectric.util.reflector.Direct;
26 import org.robolectric.util.reflector.ForType;
27 import org.robolectric.util.reflector.Static;
28 import org.robolectric.util.reflector.WithType;
29 
30 /**
31  * The shadow API for {@link android.view.Choreographer}.
32  *
33  * <p>Different shadow implementations will be used depending on the current {@link LooperMode}. See
34  * {@link ShadowLegacyChoreographer} and {@link ShadowPausedChoreographer} for details.
35  */
36 @Implements(value = Choreographer.class, shadowPicker = ShadowChoreographer.Picker.class)
37 public abstract class ShadowChoreographer {
38 
39   @RealObject Choreographer realObject;
40   private ChoreographerReflector reflector;
41 
42   private static volatile boolean isPaused = false;
43 
44   private static volatile Duration frameDelay = getDefaultFrameDelay();
45 
46   /**
47    * This field is only used when {@link #isPaused()} is true. It represents the next scheduled
48    * vsync time (with respect to the system clock). See the {@link #getNextVsyncTime()} javadoc for
49    * more details.
50    */
51   private static volatile long nextVsyncTimeNanos;
52 
53   public static class Picker extends LooperShadowPicker<ShadowChoreographer> {
54 
Picker()55     public Picker() {
56       super(ShadowLegacyChoreographer.class, ShadowPausedChoreographer.class);
57     }
58   }
59 
60   /**
61    * Sets the delay between each frame. Note that the frames use the {@link ShadowSystemClock} and
62    * so have the same fidelity, when using the paused looper mode (which is the only mode supported
63    * by {@code ShadowDisplayEventReceiver}) the clock has millisecond fidelity.
64    *
65    * <p>Reasonable delays may be 15ms (approximating 60fps ~16.6ms), 10ms (approximating 90fps
66    * ~11.1ms), and 30ms (approximating 30fps ~33.3ms). Choosing too small of a frame delay may
67    * increase runtime as animation frames will have more steps.
68    *
69    * <p>Only works in {@link LooperMode.Mode#PAUSED} looper mode.
70    */
setFrameDelay(Duration delay)71   public static void setFrameDelay(Duration delay) {
72     checkState(!ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper cannot be %s", Mode.LEGACY);
73     frameDelay = delay;
74   }
75 
76   /** See {@link #setFrameDelay(Duration)}. */
getFrameDelay()77   public static Duration getFrameDelay() {
78     checkState(!ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper cannot be %s", Mode.LEGACY);
79     return frameDelay;
80   }
81 
82   /**
83    * Sets whether posting a frame should auto advance the clock or not. When paused the clock is not
84    * auto advanced, when unpaused the clock is advanced by the frame delay every time a frame
85    * callback is added. The default is not paused.
86    *
87    * <p>Only works in {@link LooperMode.Mode#PAUSED} looper mode.
88    */
setPaused(boolean paused)89   public static void setPaused(boolean paused) {
90     checkState(!ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper cannot be %s", Mode.LEGACY);
91     isPaused = paused;
92   }
93 
94   /** See {@link #setPaused(boolean)}. */
isPaused()95   public static boolean isPaused() {
96     checkState(!ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper cannot be %s", Mode.LEGACY);
97     return isPaused;
98   }
99 
100   /**
101    * @deprecated use {@link #getNextVsyncTimeNanos()} instead
102    */
103   @Deprecated
getNextVsyncTime()104   public static long getNextVsyncTime() {
105     return Duration.ofNanos(getNextVsyncTimeNanos()).toMillis();
106   }
107 
108   /**
109    * This field is only used when {@link ShadowChoreographer#isPaused()} is true. It represents the
110    * next scheduled vsync time (with respect to the system clock). When the system clock is advanced
111    * to or beyond this time, a Choreographer frame will be triggered. It may be useful for tests to
112    * know when the next scheduled vsync time is in order to determine how long to idle the main
113    * looper in order to trigger the next Choreographer callback.
114    */
getNextVsyncTimeNanos()115   public static long getNextVsyncTimeNanos() {
116     return nextVsyncTimeNanos;
117   }
118 
setNextVsyncTimeNanos(long nextVsyncTimeNanos)119   static void setNextVsyncTimeNanos(long nextVsyncTimeNanos) {
120     ShadowChoreographer.nextVsyncTimeNanos = nextVsyncTimeNanos;
121   }
122 
123   /**
124    * Allows application to specify a fixed amount of delay when {@link #postCallback(int, Runnable,
125    * Object)} is invoked. The default delay value is 0. This can be used to avoid infinite animation
126    * tasks to be spawned when the Robolectric {@link org.robolectric.util.Scheduler} is in {@link
127    * org.robolectric.util.Scheduler.IdleState#PAUSED} mode.
128    *
129    * <p>Only supported in {@link LooperMode.Mode#LEGACY}
130    *
131    * @deprecated Use the {@link Mode#PAUSED} looper instead.
132    */
133   @Deprecated
setPostCallbackDelay(int delayMillis)134   public static void setPostCallbackDelay(int delayMillis) {
135     checkState(ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper must be %s", Mode.LEGACY);
136     ShadowLegacyChoreographer.setPostCallbackDelay(delayMillis);
137   }
138 
139   /**
140    * Allows application to specify a fixed amount of delay when {@link
141    * #postFrameCallback(FrameCallback)} is invoked. The default delay value is 0. This can be used
142    * to avoid infinite animation tasks to be spawned when in LooperMode PAUSED or {@link
143    * org.robolectric.util.Scheduler.IdleState#PAUSED} and displaying an animation.
144    *
145    * @deprecated Use the {@link Mode#PAUSED} looper and {@link #setPaused(boolean)} and {@link
146    *     #setFrameDelay(Duration)} to configure the vsync event behavior.
147    */
148   @Deprecated
setPostFrameCallbackDelay(int delayMillis)149   public static void setPostFrameCallbackDelay(int delayMillis) {
150     if (looperMode() == Mode.LEGACY) {
151       ShadowLegacyChoreographer.setPostFrameCallbackDelay(delayMillis);
152     } else {
153       setPaused(delayMillis != 0);
154       setFrameDelay(Duration.ofMillis(delayMillis == 0 ? 1 : delayMillis));
155     }
156   }
157 
158   /**
159    * Return the current inter-frame interval.
160    *
161    * <p>Can only be used in {@link LooperMode.Mode#LEGACY}
162    *
163    * @return Inter-frame interval.
164    * @deprecated Use the {@link Mode#PAUSED} looper and {@link #getFrameDelay()} to configure the
165    *     frame delay.
166    */
167   @Deprecated
getFrameInterval()168   public static long getFrameInterval() {
169     checkState(ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper must be %s", Mode.LEGACY);
170     return ShadowLegacyChoreographer.getFrameInterval();
171   }
172 
173   /**
174    * Set the inter-frame interval used to advance the clock. By default, this is set to 1ms.
175    *
176    * <p>Only supported in {@link LooperMode.Mode#LEGACY}
177    *
178    * @param frameInterval Inter-frame interval.
179    * @deprecated Use the {@link Mode#PAUSED} looper and {@link #setFrameDelay(Duration)} to
180    *     configure the frame delay.
181    */
182   @Deprecated
setFrameInterval(long frameInterval)183   public static void setFrameInterval(long frameInterval) {
184     checkState(ShadowLooper.looperMode().equals(Mode.LEGACY), "Looper must be %s", Mode.LEGACY);
185     ShadowLegacyChoreographer.setFrameInterval(frameInterval);
186   }
187 
188   @Implementation(maxSdk = R)
doFrame(long frameTimeNanos, int frame)189   protected void doFrame(long frameTimeNanos, int frame) {
190     if (reflector == null) {
191       reflector = reflector(ChoreographerReflector.class, realObject);
192     }
193     PerfStatsCollector.getInstance()
194         .measure("doFrame", () -> reflector.doFrame(frameTimeNanos, frame));
195   }
196 
197   @Implementation(minSdk = S)
doFrame( long frameTimeNanos, int frame, @ClassName("android.view.DisplayEventReceiver$VsyncEventData") Object vsyncEventData)198   protected void doFrame(
199       long frameTimeNanos,
200       int frame,
201       @ClassName("android.view.DisplayEventReceiver$VsyncEventData") Object vsyncEventData) {
202     if (reflector == null) {
203       reflector = reflector(ChoreographerReflector.class, realObject);
204     }
205     PerfStatsCollector.getInstance()
206         .measure("doFrame", () -> reflector.doFrame(frameTimeNanos, frame, vsyncEventData));
207   }
208 
209   @Resetter
reset()210   public static void reset() {
211     nextVsyncTimeNanos = 0;
212     isPaused = false;
213     frameDelay = getDefaultFrameDelay();
214     if (RuntimeEnvironment.getApiLevel() >= N) {
215       ShadowBackdropFrameRenderer.reset();
216     }
217   }
218 
getDefaultFrameDelay()219   private static Duration getDefaultFrameDelay() {
220     // Uses 15ms to approximate 60fps.
221     return Duration.ofMillis(Integer.getInteger("robolectric.defaultFrameDelayMs", 15));
222   }
223 
224   /** Accessor interface for {@link Choreographer}'s internals */
225   @ForType(Choreographer.class)
226   protected interface ChoreographerReflector {
227 
228     @Accessor("mLastFrameTimeNanos")
setLastFrameTimeNanos(long time)229     void setLastFrameTimeNanos(long time);
230 
231     @Accessor("mCallbackQueues")
getCallbackQueues()232     Object[] getCallbackQueues();
233 
234     @Accessor("mCallbackPool")
setCallbackPool(Object callbackPool)235     void setCallbackPool(Object callbackPool);
236 
237     @Accessor("mFrameScheduled")
setFrameScheduled(boolean frameScheduled)238     void setFrameScheduled(boolean frameScheduled);
239 
240     @Accessor("mCallbacksRunning")
setCallbacksRunning(boolean callbacksRunning)241     void setCallbacksRunning(boolean callbacksRunning);
242 
243     @Direct
doFrame(long frameTimeNanos, int frame)244     void doFrame(long frameTimeNanos, int frame);
245 
246     @Direct
doFrame( long frameTimeNanos, int frame, @WithType("android.view.DisplayEventReceiver$VsyncEventData") Object vsyncEventData)247     void doFrame(
248         long frameTimeNanos,
249         int frame,
250         @WithType("android.view.DisplayEventReceiver$VsyncEventData") Object vsyncEventData);
251 
252     @Accessor("mDisplayEventReceiver")
getReceiver()253     DisplayEventReceiver getReceiver();
254 
255     @Accessor("sThreadInstance")
256     @Static
getThreadInstance()257     ThreadLocal<Choreographer> getThreadInstance();
258 
259     @Accessor("mLooper")
getLooper()260     Looper getLooper();
261 
262     @Direct
__constructor__(Looper looper)263     void __constructor__(Looper looper);
264 
265     @Direct
__constructor__(Looper looper, int vsyncSource, long layerHandle)266     void __constructor__(Looper looper, int vsyncSource, long layerHandle);
267 
268     @Direct
__constructor__(Looper looper, int vsyncSource)269     void __constructor__(Looper looper, int vsyncSource);
270 
271     @Accessor("mFrameData")
getFrameData()272     /*android.view.Choreographer$FrameData*/ Object getFrameData();
273 
274     @Accessor("mLastFrameIntervalNanos")
setLastFrameIntervalNanos(long val)275     void setLastFrameIntervalNanos(long val);
276 
277     @Accessor("mFrameInfo")
getFrameInfo()278     Object /* FrameInfo */ getFrameInfo();
279   }
280 
281   /** Accessor interface for {@link Choreographer}'s CallbackQueue internals */
282   @ForType(className = "android.view.Choreographer$CallbackQueue")
283   protected interface CallbackQueueReflector {
284     @Accessor("mHead")
setHead(Object head)285     void setHead(Object head);
286   }
287 }
288