All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowLegacyChoreographer Maven / Gradle / Ivy

package org.robolectric.shadows;

import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.SoftThreadLocal;
import org.robolectric.util.TimeUtils;

/**
 * The {@link Choreographer} shadow for {@link LooperMode.Mode.PAUSED}.
 *
 * 

In {@link LooperMode.Mode.PAUSED} mode, Robolectric maintains its own concept of the current * time from the Choreographer's point of view, aimed at making animations work correctly. Time * starts out at 0 and advances by {@code frameInterval} every time {@link * Choreographer#getFrameTimeNanos()} is called. */ @Implements( value = Choreographer.class, shadowPicker = ShadowChoreographer.Picker.class, isInAndroidSdk = false) public class ShadowLegacyChoreographer extends ShadowChoreographer { private long nanoTime = 0; private static long FRAME_INTERVAL = 10 * TimeUtils.NANOS_PER_MS; // 10ms private static final Thread MAIN_THREAD = Thread.currentThread(); private static SoftThreadLocal instance = makeThreadLocal(); private Handler handler = new Handler(Looper.myLooper()); private static volatile int postCallbackDelayMillis = 0; private static volatile int postFrameCallbackDelayMillis = 0; @SuppressWarnings("ReturnValueIgnored") private static SoftThreadLocal makeThreadLocal() { return new SoftThreadLocal() { @Override protected Choreographer create() { Looper looper = Looper.myLooper(); if (looper == null) { throw new IllegalStateException("The current thread must have a looper!"); } // Choreographer's constructor changes somewhere in Android O... try { Choreographer.class.getDeclaredConstructor(Looper.class); return Shadow.newInstance( Choreographer.class, new Class[] {Looper.class}, new Object[] {looper}); } catch (NoSuchMethodException e) { return Shadow.newInstance( Choreographer.class, new Class[] {Looper.class, int.class}, new Object[] {looper, 0}); } } }; } /** * Allows application to specify a fixed amount of delay when {@link #postCallback(int, Runnable, * Object)} is invoked. The default delay value is 0. This can be used to avoid infinite animation * tasks to be spawned when the Robolectric {@link org.robolectric.util.Scheduler} is in {@link * org.robolectric.util.Scheduler.IdleState#PAUSED} mode. */ public static void setPostCallbackDelay(int delayMillis) { postCallbackDelayMillis = delayMillis; } /** * Allows application to specify a fixed amount of delay when {@link * #postFrameCallback(FrameCallback)} is invoked. The default delay value is 0. This can be used * to avoid infinite animation tasks to be spawned when the Robolectric {@link * org.robolectric.util.Scheduler} is in {@link org.robolectric.util.Scheduler.IdleState#PAUSED} * mode. */ public static void setPostFrameCallbackDelay(int delayMillis) { postFrameCallbackDelayMillis = delayMillis; } @Implementation protected static Choreographer getInstance() { return instance.get(); } /** * The default implementation will call {@link #postCallbackDelayed(int, Runnable, Object, long)} * with no delay. {@link android.animation.AnimationHandler} calls this method to schedule * animation updates infinitely. Because during a Robolectric test the system time is paused and * execution of the event loop is invoked for each test instruction, the behavior of * AnimationHandler would result in endless looping (the execution of the task results in a new * animation task created and scheduled to the front of the event loop queue). * *

To prevent endless looping, a test may call {@link #setPostCallbackDelay(int)} to specify a * small delay when animation is scheduled. * * @see #setPostCallbackDelay(int) */ @Implementation protected void postCallback(int callbackType, Runnable action, Object token) { postCallbackDelayed(callbackType, action, token, postCallbackDelayMillis); } @Implementation protected void postCallbackDelayed( int callbackType, Runnable action, Object token, long delayMillis) { handler.postDelayed(action, delayMillis); } @Implementation protected void removeCallbacks(int callbackType, Runnable action, Object token) { handler.removeCallbacks(action, token); } /** * The default implementation will call {@link #postFrameCallbackDelayed(FrameCallback, long)} * with no delay. {@link android.animation.AnimationHandler} calls this method to schedule * animation updates infinitely. Because during a Robolectric test the system time is paused and * execution of the event loop is invoked for each test instruction, the behavior of * AnimationHandler would result in endless looping (the execution of the task results in a new * animation task created and scheduled to the front of the event loop queue). * *

To prevent endless looping, a test may call {@link #setPostFrameCallbackDelay(int)} to * specify a small delay when animation is scheduled. * * @see #setPostCallbackDelay(int) */ @Implementation protected void postFrameCallback(final FrameCallback callback) { postFrameCallbackDelayed(callback, postFrameCallbackDelayMillis); } @Implementation protected void postFrameCallbackDelayed(final FrameCallback callback, long delayMillis) { handler.postAtTime( new Runnable() { @Override public void run() { callback.doFrame(getFrameTimeNanos()); } }, callback, SystemClock.uptimeMillis() + delayMillis); } @Implementation protected void removeFrameCallback(FrameCallback callback) { handler.removeCallbacksAndMessages(callback); } @Implementation protected long getFrameTimeNanos() { final long now = nanoTime; nanoTime += ShadowLegacyChoreographer.FRAME_INTERVAL; return now; } /** * Return the current inter-frame interval. * * @return Inter-frame interval. */ public static long getFrameInterval() { return ShadowLegacyChoreographer.FRAME_INTERVAL; } /** * Set the inter-frame interval used to advance the clock. By default, this is set to 1ms. * * @param frameInterval Inter-frame interval. */ public static void setFrameInterval(long frameInterval) { ShadowLegacyChoreographer.FRAME_INTERVAL = frameInterval; } @Resetter public static synchronized void reset() { // Blech. We need to share the main looper because somebody might refer to it in a static // field. We also need to keep it in a soft reference so we don't max out permgen. if (Thread.currentThread() != MAIN_THREAD) { throw new RuntimeException("You should only call this from the main thread!"); } instance = makeThreadLocal(); FRAME_INTERVAL = 10 * TimeUtils.NANOS_PER_MS; // 10ms } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy