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