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