1 package org.robolectric.shadows;
2 
3 import static org.robolectric.util.reflector.Reflector.reflector;
4 
5 import android.graphics.FrameInfo;
6 import android.os.Build.VERSION_CODES;
7 import android.os.Looper;
8 import android.view.Choreographer;
9 import android.view.DisplayEventReceiver;
10 import com.android.internal.annotations.VisibleForTesting;
11 import java.util.Arrays;
12 import java.util.Set;
13 import java.util.concurrent.CopyOnWriteArraySet;
14 import org.robolectric.RuntimeEnvironment;
15 import org.robolectric.annotation.Implementation;
16 import org.robolectric.annotation.Implements;
17 import org.robolectric.annotation.LooperMode;
18 import org.robolectric.annotation.RealObject;
19 import org.robolectric.shadow.api.Shadow;
20 import org.robolectric.shadows.ShadowDisplayEventReceiver.DisplayEventReceiverReflector;
21 import org.robolectric.util.ReflectionHelpers;
22 import org.robolectric.util.ReflectionHelpers.ClassParameter;
23 import org.robolectric.versioning.AndroidVersions.NMR1;
24 import org.robolectric.versioning.AndroidVersions.O;
25 import org.robolectric.versioning.AndroidVersions.Q;
26 import org.robolectric.versioning.AndroidVersions.S;
27 import org.robolectric.versioning.AndroidVersions.T;
28 import org.robolectric.versioning.AndroidVersions.U;
29 
30 /**
31  * A {@link Choreographer} shadow for {@link LooperMode.Mode.PAUSED}.
32  *
33  * <p>This shadow is largely a no-op. In {@link LooperMode.Mode.PAUSED} mode, the shadowing is done
34  * at a lower level via {@link ShadowDisplayEventReceiver}.
35  *
36  * <p>This class should not be referenced directly - use {@link ShadowChoreographer} instead.
37  */
38 @Implements(
39     value = Choreographer.class,
40     shadowPicker = ShadowChoreographer.Picker.class,
41     isInAndroidSdk = false)
42 public class ShadowPausedChoreographer extends ShadowChoreographer {
43 
44   // keep track of all active Choreographers so they can be selectively reset
45   private static final Set<Choreographer> activeChoreographers = new CopyOnWriteArraySet<>();
46 
47   @RealObject private Choreographer realChoreographer;
48 
49   @Implementation(maxSdk = NMR1.SDK_INT)
__constructor__(Looper looper)50   protected void __constructor__(Looper looper) {
51     reflector(ChoreographerReflector.class, realChoreographer).__constructor__(looper);
52     activeChoreographers.add(realChoreographer);
53   }
54 
55   @Implementation(minSdk = O.SDK_INT, maxSdk = T.SDK_INT)
__constructor__(Looper looper, int vsyncSource)56   protected void __constructor__(Looper looper, int vsyncSource) {
57     reflector(ChoreographerReflector.class, realChoreographer).__constructor__(looper, vsyncSource);
58     activeChoreographers.add(realChoreographer);
59   }
60 
61   @Implementation(minSdk = U.SDK_INT)
__constructor__(Looper looper, int vsyncSource, long layerHandle)62   protected void __constructor__(Looper looper, int vsyncSource, long layerHandle) {
63     reflector(ChoreographerReflector.class, realChoreographer)
64         .__constructor__(looper, vsyncSource, layerHandle);
65     activeChoreographers.add(realChoreographer);
66   }
67 
68   @Implementation(minSdk = VERSION_CODES.N)
dispose()69   protected void dispose() {
70     activeChoreographers.remove(realChoreographer);
71   }
72 
73   /**
74    * Resets the Choreographer state
75    *
76    * <p>Called from ShadowPausedLooper reset to ensure this occurs before Loopers are reset.
77    */
resetChoreographers()78   static void resetChoreographers() {
79     for (Choreographer choreographer : activeChoreographers) {
80       Looper looper = reflector(ChoreographerReflector.class, choreographer).getLooper();
81       ShadowPausedChoreographer shadowChoreographer = Shadow.extract(choreographer);
82       if (looper.getThread() == Thread.currentThread()) {
83         shadowChoreographer.resetState();
84       } else if (looper.getThread().isAlive()) {
85         ShadowPausedLooper shadowLooper = Shadow.extract(looper);
86         shadowLooper.postSyncQuiet(shadowChoreographer::resetState);
87       }
88     }
89   }
90 
resetState()91   private void resetState() {
92     ChoreographerReflector choreographerReflector =
93         reflector(ChoreographerReflector.class, realChoreographer);
94     choreographerReflector.setLastFrameTimeNanos(Long.MIN_VALUE);
95     if (RuntimeEnvironment.getApiLevel() >= S.SDK_INT) {
96       choreographerReflector.setLastFrameIntervalNanos(0);
97     }
98     choreographerReflector.setFrameScheduled(false);
99     Object[] /* CallbackQueue */ callbackQueues = choreographerReflector.getCallbackQueues();
100     for (Object callbackQueue : callbackQueues) {
101       reflector(CallbackQueueReflector.class, callbackQueue).setHead(null);
102     }
103     choreographerReflector.setCallbackPool(null);
104     choreographerReflector.setCallbacksRunning(false);
105     if (RuntimeEnvironment.getApiLevel() >= U.SDK_INT) {
106       ReflectionHelpers.callInstanceMethod(
107           choreographerReflector.getFrameData(),
108           "update",
109           ClassParameter.from(long.class, 0),
110           ClassParameter.from(int.class, 0));
111     }
112 
113     if (RuntimeEnvironment.getApiLevel() >= Q.SDK_INT) {
114       Arrays.fill(((FrameInfo) choreographerReflector.getFrameInfo()).frameInfo, 0);
115     }
116     DisplayEventReceiver receiver =
117         reflector(ChoreographerReflector.class, realObject).getReceiver();
118     ShadowDisplayEventReceiver shadowReceiver = Shadow.extract(receiver);
119     shadowReceiver.resetState();
120   }
121 
122   /**
123    * Returns true if choreographer has been initialized properly.
124    *
125    * @return
126    */
127   @VisibleForTesting
isInitialized()128   boolean isInitialized() {
129     DisplayEventReceiver receiver =
130         reflector(ChoreographerReflector.class, realObject).getReceiver();
131     return reflector(DisplayEventReceiverReflector.class, receiver).getReceiverPtr() != 0;
132   }
133 }
134