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

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

The newest version!
package org.robolectric.shadows;

import static org.robolectric.RuntimeEnvironment.isMainThread;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;

import android.os.Looper;
import android.os.MessageQueue;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
import org.robolectric.RoboSettings;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.config.ConfigurationRegistry;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.Scheduler;

/**
 * The shadow Looper implementation for {@link LooperMode.Mode.LEGACY}.
 *
 * 

Robolectric enqueues posted {@link Runnable}s to be run (on this thread) later. {@code * Runnable}s that are scheduled to run immediately can be triggered by calling {@link #idle()}. * * @see ShadowMessageQueue */ @Implements(value = Looper.class, isInAndroidSdk = false) @SuppressWarnings("SynchronizeOnNonFinalField") public class ShadowLegacyLooper extends ShadowLooper { // Replaced SoftThreadLocal with a WeakHashMap, because ThreadLocal make it impossible to access // their contents from other threads, but we need to be able to access the loopers for all // threads so that we can shut them down when resetThreadLoopers() // is called. This also allows us to implement the useful getLooperForThread() method. // Note that the main looper is handled differently and is not put in this hash, because we need // to be able to "switch" the thread that the main looper is associated with. private static Map loopingLoopers = Collections.synchronizedMap(new WeakHashMap()); private static Looper mainLooper; private static Scheduler backgroundScheduler; private @RealObject Looper realObject; boolean quit; @Resetter public static synchronized void resetThreadLoopers() { // do not use looperMode() here, because its cached value might already have been reset if (ConfigurationRegistry.get(LooperMode.Mode.class) != LooperMode.Mode.LEGACY) { // ignore if realistic looper return; } synchronized (loopingLoopers) { for (Looper looper : loopingLoopers.values()) { synchronized (looper) { if (!shadowOf(looper).quit) { looper.quit(); } else { // Reset the schedulers of all loopers. This prevents un-run tasks queued up in static // background handlers from leaking to subsequent tests. shadowOf(looper).getScheduler().reset(); shadowOf(looper.getQueue()).reset(); } } } } // Because resetStaticState() is called by AndroidTestEnvironment on startup before // prepareMainLooper() is called, this might be null on that occasion. if (mainLooper != null) { shadowOf(mainLooper).reset(); } } static synchronized Scheduler getBackgroundThreadScheduler() { return backgroundScheduler; } /** Internal API to initialize background thread scheduler from AndroidTestEnvironment. */ public static void internalInitializeBackgroundThreadScheduler() { backgroundScheduler = RoboSettings.isUseGlobalScheduler() ? RuntimeEnvironment.getMasterScheduler() : new Scheduler(); } @Implementation protected void __constructor__(boolean quitAllowed) { invokeConstructor(Looper.class, realObject, from(boolean.class, quitAllowed)); if (isMainThread()) { mainLooper = realObject; } else { loopingLoopers.put(Thread.currentThread(), realObject); } resetScheduler(); } @Implementation protected static Looper getMainLooper() { return mainLooper; } @Implementation protected static Looper myLooper() { return getLooperForThread(Thread.currentThread()); } @Implementation protected static void loop() { shadowOf(Looper.myLooper()).doLoop(); } private void doLoop() { if (realObject != Looper.getMainLooper()) { synchronized (realObject) { while (!quit) { try { realObject.wait(); } catch (InterruptedException ignore) { } } } } } @Implementation protected void quit() { if (realObject == Looper.getMainLooper()) { throw new RuntimeException("Main thread not allowed to quit"); } quitUnchecked(); } @Implementation protected void quitSafely() { quit(); } @Override public void quitUnchecked() { synchronized (realObject) { quit = true; realObject.notifyAll(); getScheduler().reset(); shadowOf(realObject.getQueue()).reset(); } } @Override public boolean hasQuit() { synchronized (realObject) { return quit; } } public static Looper getLooperForThread(Thread thread) { return isMainThread(thread) ? mainLooper : loopingLoopers.get(thread); } /** Return loopers for all threads including main thread. */ protected static Collection getLoopers() { List loopers = new ArrayList<>(loopingLoopers.values()); loopers.add(mainLooper); return Collections.unmodifiableCollection(loopers); } @Override public void idle() { idle(0, TimeUnit.MILLISECONDS); } @Override public void idleFor(long time, TimeUnit timeUnit) { getScheduler().advanceBy(time, timeUnit); } @Override public boolean isIdle() { return !getScheduler().areAnyRunnable(); } @Override public void idleIfPaused() { // ignore } @Override public void idleConstantly(boolean shouldIdleConstantly) { getScheduler().idleConstantly(shouldIdleConstantly); } @Override public void runToEndOfTasks() { getScheduler().advanceToLastPostedRunnable(); } @Override public void runToNextTask() { getScheduler().advanceToNextPostedRunnable(); } @Override public void runOneTask() { getScheduler().runOneTask(); } /** * Enqueue a task to be run later. * * @param runnable the task to be run * @param delayMillis how many milliseconds into the (virtual) future to run it * @return true if the runnable is enqueued * @see android.os.Handler#postDelayed(Runnable,long) * @deprecated Use a {@link android.os.Handler} instance to post to a looper. */ @Override @Deprecated public boolean post(Runnable runnable, long delayMillis) { if (!quit) { getScheduler().postDelayed(runnable, delayMillis, TimeUnit.MILLISECONDS); return true; } else { return false; } } /** * Enqueue a task to be run ahead of all other delayed tasks. * * @param runnable the task to be run * @return true if the runnable is enqueued * @see android.os.Handler#postAtFrontOfQueue(Runnable) * @deprecated Use a {@link android.os.Handler} instance to post to a looper. */ @Override @Deprecated public boolean postAtFrontOfQueue(Runnable runnable) { if (!quit) { getScheduler().postAtFrontOfQueue(runnable); return true; } else { return false; } } @Override public void pause() { getScheduler().pause(); } @Override public Duration getNextScheduledTaskTime() { return getScheduler().getNextScheduledTaskTime(); } @Override public Duration getLastScheduledTaskTime() { return getScheduler().getLastScheduledTaskTime(); } @Override public void unPause() { getScheduler().unPause(); } @Override public boolean isPaused() { return getScheduler().isPaused(); } @Override public boolean setPaused(boolean shouldPause) { boolean wasPaused = isPaused(); if (shouldPause) { pause(); } else { unPause(); } return wasPaused; } @Override public void resetScheduler() { ShadowMessageQueue shadowMessageQueue = shadowOf(realObject.getQueue()); if (realObject == Looper.getMainLooper() || RoboSettings.isUseGlobalScheduler()) { shadowMessageQueue.setScheduler(RuntimeEnvironment.getMasterScheduler()); } else { shadowMessageQueue.setScheduler(new Scheduler()); } } /** Causes all enqueued tasks to be discarded, and pause state to be reset */ @Override public void reset() { shadowOf(realObject.getQueue()).reset(); resetScheduler(); quit = false; } /** * Returns the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued * tasks. This scheduler is managed by the Looper's associated queue. * * @return the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued * tasks. */ @Override public Scheduler getScheduler() { return shadowOf(realObject.getQueue()).getScheduler(); } @Override public void runPaused(Runnable r) { boolean wasPaused = setPaused(true); try { r.run(); } finally { if (!wasPaused) unPause(); } } private static ShadowLegacyLooper shadowOf(Looper looper) { return Shadow.extract(looper); } private static ShadowMessageQueue shadowOf(MessageQueue mq) { return Shadow.extract(mq); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy