• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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