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

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

package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.M;
import static com.google.common.base.Preconditions.checkState;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.os.MessageQueue.IdleHandler;
import android.os.SystemClock;
import java.time.Duration;
import java.util.ArrayList;
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.res.android.NativeObjRegistry;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.Scheduler;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;

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

This class should not be referenced directly. Use {@link ShadowMessageQueue} instead. */ @SuppressWarnings("SynchronizeOnNonFinalField") @Implements(value = MessageQueue.class, isInAndroidSdk = false, looseSignatures = true) public class ShadowPausedMessageQueue extends ShadowMessageQueue { @RealObject private MessageQueue realQueue; // just use this class as the native object private static NativeObjRegistry nativeQueueRegistry = new NativeObjRegistry(ShadowPausedMessageQueue.class); private boolean isPolling = false; private ShadowPausedSystemClock.Listener clockListener; // shadow constructor instead of nativeInit because nativeInit signature has changed across SDK // versions @Implementation protected void __constructor__(boolean quitAllowed) { invokeConstructor(MessageQueue.class, realQueue, from(boolean.class, quitAllowed)); int ptr = (int) nativeQueueRegistry.register(this); reflector(MessageQueueReflector.class, realQueue).setPtr(ptr); clockListener = () -> { synchronized (realQueue) { // only wake up the Looper thread if queue is non empty to reduce contention if many // Looper threads are active if (getMessages() != null) { nativeWake(ptr); } } }; ShadowPausedSystemClock.addStaticListener(clockListener); } @Implementation(maxSdk = JELLY_BEAN_MR1) protected void nativeDestroy() { nativeDestroy(reflector(MessageQueueReflector.class, realQueue).getPtr()); } @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT) protected static void nativeDestroy(int ptr) { nativeDestroy((long) ptr); } @Implementation(minSdk = KITKAT_WATCH) protected static void nativeDestroy(long ptr) { ShadowPausedMessageQueue q = nativeQueueRegistry.unregister(ptr); ShadowPausedSystemClock.removeListener(q.clockListener); } @Implementation(maxSdk = JELLY_BEAN_MR1) protected void nativePollOnce(int ptr, int timeoutMillis) { nativePollOnce((long) ptr, timeoutMillis); } // use the generic Object parameter types here, to avoid conflicts with the non-static // nativePollOnce @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = LOLLIPOP_MR1) protected static void nativePollOnce(Object ptr, Object timeoutMillis) { long ptrLong = getLong(ptr); nativeQueueRegistry.getNativeObject(ptrLong).nativePollOnce(ptrLong, (int) timeoutMillis); } @Implementation(minSdk = M) protected void nativePollOnce(long ptr, int timeoutMillis) { if (timeoutMillis == 0) { return; } synchronized (realQueue) { // only block if queue is empty // ignore timeout since clock is not advancing. ClockListener will notify when clock advances while (isIdle() && !isQuitting()) { isPolling = true; try { realQueue.wait(); } catch (InterruptedException e) { // ignore } } isPolling = false; } } /** * Polls the message queue waiting until a message is posted to the head of the queue. This will * suspend the thread until a new message becomes available. Returns immediately if the queue is * not idle. There's no guarantee that the message queue will not still be idle when returning, * but if the message queue becomes not idle it will return immediately. * *

See {@link ShadowPausedLooper#poll(long)} for more information. * * @param timeout Timeout in milliseconds, the maximum time to wait before returning, or 0 to wait * indefinitely, */ void poll(long timeout) { checkState(Looper.myLooper() == Looper.getMainLooper() && Looper.myQueue() == realQueue); // Message queue typically expects the looper to loop calling next() which returns current // messages from the head of the queue. If no messages are current it will mark itself blocked // and call nativePollOnce (see above) which suspends the thread until the next message's time. // When messages are posted to the queue, if a new message is posted to the head and the queue // is marked as blocked, then the enqueue function will notify and resume next(), allowing it // return the next message. To simulate this behavior check if the queue is idle and if it is // mark the queue as blocked and wait on a new message. synchronized (realQueue) { if (isIdle()) { ReflectionHelpers.setField(realQueue, "mBlocked", true); try { realQueue.wait(timeout); } catch (InterruptedException ignored) { // Fall through and unblock with no messages. } finally { ReflectionHelpers.setField(realQueue, "mBlocked", false); } } } } @Implementation(maxSdk = JELLY_BEAN_MR1) protected void nativeWake(int ptr) { synchronized (realQueue) { realQueue.notifyAll(); } } // use the generic Object parameter types here, to avoid conflicts with the non-static // nativeWake @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT) protected static void nativeWake(Object ptr) { // JELLY_BEAN_MR2 has a bug where nativeWake can get called when pointer has already been // destroyed. See here where nativeWake is called outside the synchronized block // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/jb-mr2-release/core/java/android/os/MessageQueue.java#239 // So check to see if native object exists first ShadowPausedMessageQueue q = nativeQueueRegistry.peekNativeObject(getLong(ptr)); if (q != null) { q.nativeWake(getInt(ptr)); } } @Implementation(minSdk = KITKAT_WATCH) protected static void nativeWake(long ptr) { nativeQueueRegistry.getNativeObject(ptr).nativeWake((int) ptr); } @Implementation(minSdk = M) protected static boolean nativeIsPolling(long ptr) { return nativeQueueRegistry.getNativeObject(ptr).isPolling; } /** Exposes the API23+_isIdle method to older platforms */ @Implementation(minSdk = 23) public boolean isIdle() { synchronized (realQueue) { Message msg = peekNextExecutableMessage(); if (msg == null) { return true; } long now = SystemClock.uptimeMillis(); long when = shadowOfMsg(msg).getWhen(); return now < when; } } Message peekNextExecutableMessage() { MessageQueueReflector internalQueue = reflector(MessageQueueReflector.class, realQueue); Message msg = internalQueue.getMessages(); if (msg != null && shadowOfMsg(msg).getTarget() == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { msg = shadowOfMsg(msg).internalGetNext(); } while (msg != null && !msg.isAsynchronous()); } return msg; } Message getNext() { return reflector(MessageQueueReflector.class, realQueue).next(); } boolean isQuitAllowed() { return reflector(MessageQueueReflector.class, realQueue).getQuitAllowed(); } void doEnqueueMessage(Message msg, long when) { reflector(MessageQueueReflector.class, realQueue).enqueueMessage(msg, when); } Message getMessages() { return reflector(MessageQueueReflector.class, realQueue).getMessages(); } @Implementation(minSdk = M) protected boolean isPolling() { synchronized (realQueue) { return isPolling; } } @Implementation(maxSdk = JELLY_BEAN_MR1) protected void quit() { if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) { reflector(MessageQueueReflector.class, realQueue).quit(false); } else { reflector(MessageQueueReflector.class, realQueue).quit(); } } @Implementation(minSdk = JELLY_BEAN_MR2) protected void quit(boolean allowed) { reflector(MessageQueueReflector.class, realQueue).quit(allowed); ShadowPausedSystemClock.removeListener(clockListener); } private boolean isQuitting() { if (RuntimeEnvironment.getApiLevel() >= KITKAT) { return reflector(MessageQueueReflector.class, realQueue).getQuitting(); } else { return reflector(MessageQueueReflector.class, realQueue).getQuiting(); } } private static long getLong(Object intOrLongObj) { if (intOrLongObj instanceof Long) { return (long) intOrLongObj; } else { Integer intObj = (Integer) intOrLongObj; return intObj.longValue(); } } private static int getInt(Object intOrLongObj) { if (intOrLongObj instanceof Integer) { return (int) intOrLongObj; } else { Long longObj = (Long) intOrLongObj; return longObj.intValue(); } } Duration getNextScheduledTaskTime() { Message next = peekNextExecutableMessage(); if (next == null) { return Duration.ZERO; } return Duration.ofMillis(convertWhenToScheduledTime(shadowOfMsg(next).getWhen())); } Duration getLastScheduledTaskTime() { long when = 0; synchronized (realQueue) { Message next = getMessages(); if (next == null) { return Duration.ZERO; } while (next != null) { when = shadowOfMsg(next).getWhen(); next = shadowOfMsg(next).internalGetNext(); } } return Duration.ofMillis(convertWhenToScheduledTime(when)); } private static long convertWhenToScheduledTime(long when) { // in some situations, when can be 0 or less than uptimeMillis. Always floor it to at least // convertWhenToUptime if (when < SystemClock.uptimeMillis()) { when = SystemClock.uptimeMillis(); } return when; } /** * Internal method to get the number of entries in the MessageQueue. * *

Do not use, will likely be removed in a future release. */ public int internalGetSize() { int count = 0; synchronized (realQueue) { Message next = getMessages(); while (next != null) { count++; next = shadowOfMsg(next).internalGetNext(); } } return count; } /** * Returns the message at the head of the queue immediately, regardless of its scheduled time. * Compare to {@link #getNext()} which will only return the next message if the system clock is * advanced to its scheduled time. */ Message getNextIgnoringWhen() { synchronized (realQueue) { Message head = getMessages(); if (head != null) { Message next = shadowOfMsg(head).internalGetNext(); reflector(MessageQueueReflector.class, realQueue).setMessages(next); } return head; } } // TODO: reconsider exposing this as a public API. Only ShadowPausedLooper needs to access this, // so it should be package private @Override public void reset() { MessageQueueReflector msgQueue = reflector(MessageQueueReflector.class, realQueue); msgQueue.setMessages(null); msgQueue.setIdleHandlers(new ArrayList<>()); msgQueue.setNextBarrierToken(0); } private static ShadowPausedMessage shadowOfMsg(Message head) { return Shadow.extract(head); } @Override public Scheduler getScheduler() { throw new UnsupportedOperationException("Not supported in PAUSED LooperMode."); } @Override public void setScheduler(Scheduler scheduler) { throw new UnsupportedOperationException("Not supported in PAUSED LooperMode."); } // intentionally do not support direct access to MessageQueue internals @Override public Message getHead() { throw new UnsupportedOperationException("Not supported in PAUSED LooperMode."); } @Override public void setHead(Message msg) { throw new UnsupportedOperationException("Not supported in PAUSED LooperMode."); } /** * Retrieves a copy of the current list of idle handlers. Idle handlers are read with * synchronization on the real queue. */ ArrayList getIdleHandlersCopy() { synchronized (realQueue) { return new ArrayList<>(reflector(MessageQueueReflector.class, realQueue).getIdleHandlers()); } } /** Accessor interface for {@link MessageQueue}'s internals. */ @ForType(MessageQueue.class) private interface MessageQueueReflector { boolean enqueueMessage(Message msg, long when); Message next(); @Accessor("mMessages") void setMessages(Message msg); @Accessor("mMessages") Message getMessages(); @Accessor("mIdleHandlers") void setIdleHandlers(ArrayList list); @Accessor("mIdleHandlers") ArrayList getIdleHandlers(); @Accessor("mNextBarrierToken") void setNextBarrierToken(int token); @Accessor("mQuitAllowed") boolean getQuitAllowed(); @Accessor("mPtr") void setPtr(int ptr); @Accessor("mPtr") int getPtr(); // for APIs < JELLYBEAN_MR2 @Direct void quit(); @Direct void quit(boolean b); // for APIs < KITKAT @Accessor("mQuiting") boolean getQuiting(); @Accessor("mQuitting") boolean getQuitting(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy