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