| package org.robolectric.shadows; |
| |
| import android.os.Looper; |
| |
| import org.robolectric.annotation.Implementation; |
| import org.robolectric.annotation.Implements; |
| import org.robolectric.annotation.RealObject; |
| import org.robolectric.annotation.Resetter; |
| import org.robolectric.annotation.HiddenApi; |
| import org.robolectric.util.ReflectionHelpers; |
| import org.robolectric.util.Scheduler; |
| import org.robolectric.util.SoftThreadLocal; |
| |
| import static org.robolectric.Shadows.shadowOf; |
| |
| /** |
| * Shadow for {@code Looper} that 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()} |
| * todo: provide better support for advancing the clock and running queued tasks |
| */ |
| @SuppressWarnings({"UnusedDeclaration"}) |
| @Implements(Looper.class) |
| public class ShadowLooper { |
| private static final Thread MAIN_THREAD = Thread.currentThread(); |
| private static SoftThreadLocal<Looper> looperForThread = makeThreadLocalLoopers(); |
| private Scheduler scheduler = new Scheduler(); |
| private Thread myThread = Thread.currentThread(); |
| private @RealObject Looper realObject; |
| |
| boolean quit; |
| |
| private static SoftThreadLocal<Looper> makeThreadLocalLoopers() { |
| return new SoftThreadLocal<Looper>() { |
| @Override protected Looper create() { |
| return createLooper(); |
| } |
| }; |
| } |
| |
| private static Looper createLooper() { |
| return ReflectionHelpers.callConstructorReflectively(Looper.class); |
| } |
| |
| @Resetter |
| public static synchronized void resetThreadLoopers() { |
| // 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 be calling this from the main thread!"); |
| } |
| |
| Looper mainLooper = looperForThread.get(); |
| looperForThread = makeThreadLocalLoopers(); |
| looperForThread.set(mainLooper); |
| shadowOf(mainLooper).reset(); |
| } |
| |
| @Implementation |
| public static Looper getMainLooper() { |
| ShadowApplication shadowApplication = ShadowApplication.getInstance(); |
| if ((shadowApplication == null) && (Thread.currentThread() == MAIN_THREAD)) { |
| Looper mainLooper = looperForThread.get(); |
| return mainLooper; |
| } else { |
| // might still throw NullPointerException |
| // better than returning null because this fails early. |
| return shadowApplication.getMainLooper(); |
| } |
| } |
| |
| @Implementation |
| public static void loop() { |
| shadowOf(myLooper()).doLoop(); |
| } |
| |
| @Implementation |
| public static synchronized Looper myLooper() { |
| return looperForThread.get(); |
| } |
| |
| public static Scheduler getUiThreadScheduler() { |
| return shadowOf(Looper.getMainLooper()).getScheduler(); |
| } |
| |
| @HiddenApi |
| public void __constructor__() { |
| } |
| |
| private void doLoop() { |
| if (this != shadowOf(getMainLooper())) { |
| synchronized (realObject) { |
| while (!quit) { |
| try { |
| realObject.wait(); |
| } catch (InterruptedException ignore) { |
| } |
| } |
| } |
| } |
| } |
| |
| @Implementation |
| public void quit() { |
| if (this == shadowOf(getMainLooper())) throw new RuntimeException("Main thread not allowed to quit"); |
| synchronized (realObject) { |
| quit = true; |
| scheduler.reset(); |
| realObject.notifyAll(); |
| } |
| } |
| |
| @Implementation |
| public Thread getThread() { |
| return myThread; |
| } |
| |
| @HiddenApi @Implementation |
| public int postSyncBarrier() { |
| return 1; |
| } |
| |
| @HiddenApi @Implementation |
| public void removeSyncBarrier(int token) { |
| } |
| |
| public boolean hasQuit() { |
| synchronized (realObject) { |
| return quit; |
| } |
| } |
| |
| public static void pauseLooper(Looper looper) { |
| shadowOf(looper).pause(); |
| } |
| |
| public static void unPauseLooper(Looper looper) { |
| shadowOf(looper).unPause(); |
| } |
| |
| public static void pauseMainLooper() { |
| pauseLooper(Looper.getMainLooper()); |
| } |
| |
| public static void unPauseMainLooper() { |
| unPauseLooper(Looper.getMainLooper()); |
| } |
| |
| public static void idleMainLooper(long interval) { |
| shadowOf(Looper.getMainLooper()).idle(interval); |
| } |
| |
| |
| public static void idleMainLooperConstantly(boolean shouldIdleConstantly) { |
| shadowOf(Looper.getMainLooper()).idleConstantly(shouldIdleConstantly); |
| } |
| |
| /** |
| * Runs any immediately runnable tasks previously queued on the UI thread, |
| * e.g. by {@link android.app.Activity#runOnUiThread(Runnable)} or {@link android.os.AsyncTask#onPostExecute(Object)}. |
| * <p/> |
| * <p/> |
| * Note: calling this method does not pause or un-pause the scheduler. |
| */ |
| public static void runUiThreadTasks() { |
| getUiThreadScheduler().advanceBy(0); |
| } |
| |
| public static void runUiThreadTasksIncludingDelayedTasks() { |
| getUiThreadScheduler().advanceToLastPostedRunnable(); |
| } |
| |
| /** |
| * Causes {@link Runnable}s that have been scheduled to run immediately to actually run. Does not advance the |
| * scheduler's clock; |
| */ |
| public void idle() { |
| scheduler.advanceBy(0); |
| } |
| |
| /** |
| * Causes {@link Runnable}s that have been scheduled to run within the next {@code intervalMillis} milliseconds to |
| * run while advancing the scheduler's clock. |
| * |
| * @param intervalMillis milliseconds to advance |
| */ |
| public void idle(long intervalMillis) { |
| scheduler.advanceBy(intervalMillis); |
| } |
| |
| public void idleConstantly(boolean shouldIdleConstantly) { |
| scheduler.idleConstantly(shouldIdleConstantly); |
| } |
| |
| /** |
| * Causes all of the {@link Runnable}s that have been scheduled to run while advancing the scheduler's clock to the |
| * start time of the last scheduled {@link Runnable}. |
| */ |
| public void runToEndOfTasks() { |
| scheduler.advanceToLastPostedRunnable(); |
| } |
| |
| /** |
| * Causes the next {@link Runnable}(s) that have been scheduled to run while advancing the scheduler's clock to its |
| * start time. If more than one {@link Runnable} is scheduled to run at this time then they will all be run. |
| */ |
| public void runToNextTask() { |
| scheduler.advanceToNextPostedRunnable(); |
| } |
| |
| /** |
| * Causes only one of the next {@link Runnable}s that have been scheduled to run while advancing the scheduler's |
| * clock to its start time. Only one {@link Runnable} will run even if more than one has ben scheduled to run at the |
| * same time. |
| */ |
| public void runOneTask() { |
| scheduler.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 |
| */ |
| public boolean post(Runnable runnable, long delayMillis) { |
| if (!quit) { |
| scheduler.postDelayed(runnable, delayMillis); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| public boolean postAtFrontOfQueue(Runnable runnable) { |
| if (!quit) { |
| scheduler.postAtFrontOfQueue(runnable); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| public void pause() { |
| scheduler.pause(); |
| } |
| |
| public void unPause() { |
| scheduler.unPause(); |
| } |
| |
| public boolean isPaused() { |
| return scheduler.isPaused(); |
| } |
| |
| public boolean setPaused(boolean shouldPause) { |
| boolean wasPaused = isPaused(); |
| if (shouldPause) { |
| pause(); |
| } else { |
| unPause(); |
| } |
| return wasPaused; |
| } |
| |
| /** |
| * Causes all enqueued tasks to be discarded, and pause state to be reset |
| */ |
| public void reset() { |
| scheduler = new Scheduler(); |
| quit = false; |
| } |
| |
| /** |
| * Returns the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued tasks. |
| * |
| * @return the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued tasks. |
| */ |
| public Scheduler getScheduler() { |
| return scheduler; |
| } |
| |
| public void runPaused(Runnable r) { |
| boolean wasPaused = setPaused(true); |
| try { |
| r.run(); |
| } finally { |
| if (!wasPaused) unPause(); |
| } |
| } |
| } |