1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; 4 import static android.os.Build.VERSION_CODES.M; 5 import static android.os.Build.VERSION_CODES.N_MR1; 6 import static android.os.Build.VERSION_CODES.O; 7 import static android.os.Build.VERSION_CODES.Q; 8 import static android.os.Build.VERSION_CODES.R; 9 import static android.os.Build.VERSION_CODES.S; 10 import static android.os.Build.VERSION_CODES.TIRAMISU; 11 import static org.robolectric.util.reflector.Reflector.reflector; 12 13 import android.os.MessageQueue; 14 import android.os.SystemClock; 15 import android.view.Choreographer; 16 import android.view.DisplayEventReceiver; 17 import dalvik.system.CloseGuard; 18 import java.lang.ref.WeakReference; 19 import java.lang.reflect.Array; 20 import java.time.Duration; 21 import org.robolectric.RuntimeEnvironment; 22 import org.robolectric.annotation.ClassName; 23 import org.robolectric.annotation.Implementation; 24 import org.robolectric.annotation.Implements; 25 import org.robolectric.annotation.RealObject; 26 import org.robolectric.annotation.ReflectorObject; 27 import org.robolectric.res.android.NativeObjRegistry; 28 import org.robolectric.shadow.api.Shadow; 29 import org.robolectric.util.ReflectionHelpers; 30 import org.robolectric.util.reflector.Accessor; 31 import org.robolectric.util.reflector.Constructor; 32 import org.robolectric.util.reflector.Direct; 33 import org.robolectric.util.reflector.ForType; 34 import org.robolectric.util.reflector.WithType; 35 import org.robolectric.versioning.AndroidVersions.Baklava; 36 import org.robolectric.versioning.AndroidVersions.U; 37 38 /** 39 * Shadow of {@link DisplayEventReceiver}. The {@link Choreographer} is a subclass of {@link 40 * DisplayEventReceiver}, and receives vsync events from the display indicating the frequency that 41 * frames should be generated. 42 * 43 * <p>The {@code ShadowDisplayEventReceiver} can run in either a paused mode or a non-paused mode, 44 * see {@link ShadowChoreographer#isPaused()} and {@link ShadowChoreographer#setPaused(boolean)}. By 45 * default it runs unpaused, and each time a frame callback is scheduled with the {@link 46 * Choreographer} the clock is advanced to the next frame, configured by {@link 47 * ShadowChoreographer#setFrameDelay(Duration)}. In paused mode the clock is not auto advanced and 48 * the next frame will only trigger when the clock is advance manually or via the {@link 49 * ShadowLooper}. 50 */ 51 @Implements(className = "android.view.DisplayEventReceiver", isInAndroidSdk = false) 52 public class ShadowDisplayEventReceiver { 53 54 private static NativeObjRegistry<NativeDisplayEventReceiver> nativeObjRegistry = 55 new NativeObjRegistry<>(NativeDisplayEventReceiver.class); 56 57 @RealObject protected DisplayEventReceiver realReceiver; 58 @ReflectorObject private DisplayEventReceiverReflector displayEventReceiverReflector; 59 60 @Implementation(minSdk = O, maxSdk = Q) nativeInit( WeakReference<DisplayEventReceiver> receiver, MessageQueue msgQueue, int vsyncSource)61 protected static long nativeInit( 62 WeakReference<DisplayEventReceiver> receiver, MessageQueue msgQueue, int vsyncSource) { 63 return nativeObjRegistry.register(new NativeDisplayEventReceiver(receiver)); 64 } 65 66 @Implementation(minSdk = M, maxSdk = N_MR1) nativeInit( WeakReference<DisplayEventReceiver> receiver, MessageQueue msgQueue)67 protected static long nativeInit( 68 WeakReference<DisplayEventReceiver> receiver, MessageQueue msgQueue) { 69 return nativeObjRegistry.register(new NativeDisplayEventReceiver(receiver)); 70 } 71 72 @Implementation(maxSdk = LOLLIPOP_MR1) nativeInit(DisplayEventReceiver receiver, MessageQueue msgQueue)73 protected static long nativeInit(DisplayEventReceiver receiver, MessageQueue msgQueue) { 74 return nativeObjRegistry.register( 75 new NativeDisplayEventReceiver(new WeakReference<>(receiver))); 76 } 77 78 @Implementation(minSdk = R, maxSdk = TIRAMISU) nativeInit( WeakReference<DisplayEventReceiver> receiver, MessageQueue msgQueue, int vsyncSource, int configChanged)79 protected static long nativeInit( 80 WeakReference<DisplayEventReceiver> receiver, 81 MessageQueue msgQueue, 82 int vsyncSource, 83 int configChanged) { 84 return nativeInit(receiver, msgQueue); 85 } 86 87 @Implementation(minSdk = U.SDK_INT) nativeInit( WeakReference<DisplayEventReceiver> receiver, WeakReference<Object> vsyncEventData, MessageQueue msgQueue, int vsyncSource, int eventRegistration, long layerHandle)88 protected static long nativeInit( 89 WeakReference<DisplayEventReceiver> receiver, 90 WeakReference<Object> vsyncEventData, 91 MessageQueue msgQueue, 92 int vsyncSource, 93 int eventRegistration, 94 long layerHandle) { 95 return nativeInit(receiver, msgQueue); 96 } 97 98 @Implementation(maxSdk = TIRAMISU) nativeDispose(long receiverPtr)99 protected static void nativeDispose(long receiverPtr) { 100 NativeDisplayEventReceiver receiver = nativeObjRegistry.unregister(receiverPtr); 101 if (receiver != null) { 102 receiver.dispose(); 103 } 104 } 105 106 @Implementation nativeScheduleVsync(long receiverPtr)107 protected static void nativeScheduleVsync(long receiverPtr) { 108 nativeObjRegistry.getNativeObject(receiverPtr).scheduleVsync(); 109 } 110 111 @Implementation(maxSdk = R) dispose(boolean finalized)112 protected void dispose(boolean finalized) { 113 CloseGuard closeGuard = displayEventReceiverReflector.getCloseGuard(); 114 // Suppresses noisy CloseGuard warning 115 if (closeGuard != null) { 116 closeGuard.close(); 117 } 118 displayEventReceiverReflector.dispose(finalized); 119 } 120 onVsync()121 protected void onVsync() { 122 if (RuntimeEnvironment.getApiLevel() < Q) { 123 displayEventReceiverReflector.onVsync( 124 ShadowSystem.nanoTime(), 0, /* SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN */ 1); 125 } else if (RuntimeEnvironment.getApiLevel() < S) { 126 displayEventReceiverReflector.onVsync( 127 ShadowSystem.nanoTime(), 0L, /* SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN */ 1); 128 } else if (RuntimeEnvironment.getApiLevel() < TIRAMISU) { 129 displayEventReceiverReflector.onVsync( 130 ShadowSystem.nanoTime(), 131 0L, /* physicalDisplayId currently ignored */ 132 /* frame= */ 1, 133 newVsyncEventData() /* VsyncEventData */); 134 } else { 135 displayEventReceiverReflector.onVsync( 136 ShadowSystem.nanoTime(), 137 0L, /* physicalDisplayId currently ignored */ 138 1, /* frame */ 139 newVsyncEventData() /* VsyncEventData */); 140 } 141 } 142 resetState()143 void resetState() { 144 if (realReceiver.getClass().getName().contains("FrameDisplayEventReceiver")) { 145 FrameDisplayEventReceiverReflector frameReflector = 146 reflector(FrameDisplayEventReceiverReflector.class, realReceiver); 147 frameReflector.setFrame(0); 148 frameReflector.setHavePendingVsync(false); 149 frameReflector.setTimestampNanos(0); 150 } 151 } 152 153 /** 154 * A simulation of the native code that provides synchronization with the display hardware frames 155 * (aka vsync), that attempts to provide relatively accurate behavior, while adjusting for 156 * Robolectric's fixed system clock. 157 * 158 * <p>In the default mode, requests for a vsync callback will be processed immediately inline. The 159 * system clock is also auto advanced by VSYNC_DELAY to appease the calling Choreographer that 160 * expects an advancing system clock. This mode allows seamless view layout / traversal operations 161 * with a simple {@link ShadowLooper#idle()} call. 162 * 163 * <p>However, the default mode can cause problems with animations which continually request vsync 164 * callbacks, leading to timeouts and hamper attempts to verify animations in progress. For those 165 * use cases, an 'async' callback mode is provided (via the {@link 166 * ShadowChoreographer#setPostFrameCallbackDelay(int)} API. In this mode, vsync requests will be 167 * scheduled asynchronously by listening to clock updates. 168 */ 169 private static class NativeDisplayEventReceiver { 170 171 private final WeakReference<DisplayEventReceiver> receiverRef; 172 private final ShadowPausedSystemClock.Listener clockListener = this::onClockAdvanced; 173 NativeDisplayEventReceiver(WeakReference<DisplayEventReceiver> receiverRef)174 public NativeDisplayEventReceiver(WeakReference<DisplayEventReceiver> receiverRef) { 175 this.receiverRef = receiverRef; 176 // register a clock listener for the async mode 177 ShadowPausedSystemClock.addStaticListener(clockListener); 178 } 179 onClockAdvanced()180 private void onClockAdvanced() { 181 synchronized (this) { 182 long nextVsyncTime = ShadowChoreographer.getNextVsyncTime(); 183 if (nextVsyncTime == 0 || ShadowPausedSystemClock.uptimeMillis() < nextVsyncTime) { 184 return; 185 } 186 ShadowChoreographer.setNextVsyncTime(0); 187 } 188 189 doVsync(); 190 } 191 dispose()192 void dispose() { 193 ShadowPausedSystemClock.removeListener(clockListener); 194 } 195 scheduleVsync()196 public void scheduleVsync() { 197 Duration frameDelay = ShadowChoreographer.getFrameDelay(); 198 if (ShadowChoreographer.isPaused()) { 199 if (ShadowChoreographer.getNextVsyncTime() < SystemClock.uptimeMillis()) { 200 ShadowChoreographer.setNextVsyncTime(SystemClock.uptimeMillis() + frameDelay.toMillis()); 201 } 202 } else { 203 // simulate an immediate callback 204 ShadowSystemClock.advanceBy(frameDelay); 205 doVsync(); 206 } 207 } 208 doVsync()209 private void doVsync() { 210 DisplayEventReceiver receiver = receiverRef.get(); 211 if (receiver != null) { 212 ShadowDisplayEventReceiver shadowReceiver = Shadow.extract(receiver); 213 shadowReceiver.onVsync(); 214 } 215 } 216 } 217 218 @Implementation(minSdk = TIRAMISU) 219 protected @ClassName("android.view.DisplayEventReceiver$VsyncEventData") Object getLatestVsyncEventData()220 getLatestVsyncEventData() { 221 return newVsyncEventData(); 222 } 223 newVsyncEventData()224 private static Object /* VsyncEventData */ newVsyncEventData() { 225 VsyncEventDataReflector vsyncEventDataReflector = reflector(VsyncEventDataReflector.class); 226 if (RuntimeEnvironment.getApiLevel() < TIRAMISU) { 227 return vsyncEventDataReflector.newVsyncEventData( 228 /* id= */ 1, /* frameDeadline= */ 10, /* frameInterval= */ 1); 229 } 230 try { 231 // onVsync on T takes a package-private VsyncEventData class, which is itself composed of a 232 // package private VsyncEventData.FrameTimeline class. So use reflection to build these up 233 Class<?> frameTimelineClass = 234 Class.forName("android.view.DisplayEventReceiver$VsyncEventData$FrameTimeline"); 235 236 int timelineArrayLength = RuntimeEnvironment.getApiLevel() == TIRAMISU ? 1 : 7; 237 FrameTimelineReflector frameTimelineReflector = reflector(FrameTimelineReflector.class); 238 Object timelineArray = Array.newInstance(frameTimelineClass, timelineArrayLength); 239 for (int i = 0; i < timelineArrayLength; i++) { 240 Array.set(timelineArray, i, frameTimelineReflector.newFrameTimeline(1, 1, 10)); 241 } 242 if (RuntimeEnvironment.getApiLevel() <= TIRAMISU) { 243 return vsyncEventDataReflector.newVsyncEventData( 244 timelineArray, /* preferredFrameTimelineIndex= */ 0, /* frameInterval= */ 1); 245 } else { 246 boolean baklavaConstructor = 247 ReflectionHelpers.hasConstructor( 248 DisplayEventReceiver.VsyncEventData.class, 249 DisplayEventReceiver.VsyncEventData.FrameTimeline[].class, 250 int.class, 251 int.class, 252 long.class, 253 int.class); 254 if (RuntimeEnvironment.getApiLevel() < Baklava.SDK_INT || !baklavaConstructor) { 255 return vsyncEventDataReflector.newVsyncEventData( 256 timelineArray, 257 /* preferredFrameTimelineIndex= */ 0, 258 timelineArrayLength, 259 /* frameInterval= */ 1); 260 } else { 261 return vsyncEventDataReflector.newVsyncEventData( 262 timelineArray, 263 /* preferredFrameTimelineIndex= */ 0, 264 timelineArrayLength, 265 /* frameInterval= */ 1, 266 /* numberQueuedBuffers= */ 0); 267 } 268 } 269 } catch (ClassNotFoundException e) { 270 throw new LinkageError("Unable to construct VsyncEventData", e); 271 } 272 } 273 274 /** Reflector interface for {@link DisplayEventReceiver}'s internals. */ 275 @ForType(DisplayEventReceiver.class) 276 protected interface DisplayEventReceiverReflector { 277 278 @Direct dispose(boolean finalized)279 void dispose(boolean finalized); 280 onVsync(long timestampNanos, int frame)281 void onVsync(long timestampNanos, int frame); 282 onVsync(long timestampNanos, int physicalDisplayId, int frame)283 void onVsync(long timestampNanos, int physicalDisplayId, int frame); 284 onVsync(long timestampNanos, long physicalDisplayId, int frame)285 void onVsync(long timestampNanos, long physicalDisplayId, int frame); 286 onVsync( long timestampNanos, long physicalDisplayId, int frame, @WithType("android.view.DisplayEventReceiver$VsyncEventData") Object vsyncEventData)287 void onVsync( 288 long timestampNanos, 289 long physicalDisplayId, 290 int frame, 291 @WithType("android.view.DisplayEventReceiver$VsyncEventData") Object vsyncEventData); 292 293 @Accessor("mCloseGuard") getCloseGuard()294 CloseGuard getCloseGuard(); 295 296 @Accessor("mReceiverPtr") getReceiverPtr()297 long getReceiverPtr(); 298 } 299 300 @ForType(className = "android.view.Choreographer$FrameDisplayEventReceiver") 301 interface FrameDisplayEventReceiverReflector { 302 @Accessor("mHavePendingVsync") setHavePendingVsync(boolean val)303 void setHavePendingVsync(boolean val); 304 305 @Accessor("mTimestampNanos") setTimestampNanos(long val)306 void setTimestampNanos(long val); 307 308 @Accessor("mFrame") setFrame(int val)309 void setFrame(int val); 310 } 311 312 @ForType(className = "android.view.DisplayEventReceiver$VsyncEventData") 313 interface VsyncEventDataReflector { 314 @Constructor newVsyncEventData(long id, long frameDeadline, long frameInterval)315 Object newVsyncEventData(long id, long frameDeadline, long frameInterval); 316 317 @Constructor newVsyncEventData( @ithType"[Landroid.view.DisplayEventReceiver$VsyncEventData$FrameTimeline;") Object frameTimelineArray, int preferredFrameTimelineIndex, long frameInterval)318 Object newVsyncEventData( 319 @WithType("[Landroid.view.DisplayEventReceiver$VsyncEventData$FrameTimeline;") 320 Object frameTimelineArray, 321 int preferredFrameTimelineIndex, 322 long frameInterval); 323 324 @Constructor newVsyncEventData( @ithType"[Landroid.view.DisplayEventReceiver$VsyncEventData$FrameTimeline;") Object frameTimelineArray, int preferredFrameTimelineIndex, int timelineArrayLength, long frameInterval)325 Object newVsyncEventData( 326 @WithType("[Landroid.view.DisplayEventReceiver$VsyncEventData$FrameTimeline;") 327 Object frameTimelineArray, 328 int preferredFrameTimelineIndex, 329 int timelineArrayLength, 330 long frameInterval); 331 332 @Constructor newVsyncEventData( @ithType"[Landroid.view.DisplayEventReceiver$VsyncEventData$FrameTimeline;") Object frameTimelineArray, int preferredFrameTimelineIndex, int timelineArrayLength, long frameInterval, int numberQueuedBuffers)333 Object newVsyncEventData( 334 @WithType("[Landroid.view.DisplayEventReceiver$VsyncEventData$FrameTimeline;") 335 Object frameTimelineArray, 336 int preferredFrameTimelineIndex, 337 int timelineArrayLength, 338 long frameInterval, 339 int numberQueuedBuffers); 340 } 341 342 @ForType(className = "android.view.DisplayEventReceiver$VsyncEventData$FrameTimeline") 343 interface FrameTimelineReflector { 344 @Constructor newFrameTimeline(long id, long expectedPresentTime, long deadline)345 Object newFrameTimeline(long id, long expectedPresentTime, long deadline); 346 } 347 } 348