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