CombinedMessageQueue add peekWhenForTest and popForTest methods

The intention here is that tests should be able to call these methods
instead of reaching into MessageQueue internals via reflection.

Test: atest MessageQueueTest
Test: atest TestLooperManagerTest
Flag: android.os.message_queue_testability
Bug: 379472827
Change-Id: I5ff9dcac41f76aeabbce51f7d6db2ed5582c5664
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 804e8fa..11b80ce 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -18,6 +18,8 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.app.Instrumentation;
@@ -27,11 +29,14 @@
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.ravenwood.annotation.RavenwoodRedirect;
 import android.ravenwood.annotation.RavenwoodRedirectionClass;
+import android.ravenwood.annotation.RavenwoodReplace;
+import android.ravenwood.annotation.RavenwoodThrow;
 import android.util.Log;
 import android.util.Printer;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.ravenwood.RavenwoodEnvironment;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -174,6 +179,28 @@
         }
     }
 
+    @RavenwoodReplace
+    private static void throwIfNotTest() {
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        if (activityThread == null) {
+            // Only tests can reach here.
+            return;
+        }
+        final Instrumentation instrumentation = activityThread.getInstrumentation();
+        if (instrumentation == null) {
+            // Only tests can reach here.
+            return;
+        }
+        if (instrumentation.isInstrumenting()) {
+            return;
+        }
+        throw new IllegalStateException("Test-only API called not from a test!");
+    }
+
+    private static void throwIfNotTest$ravenwood() {
+        return;
+    }
+
     private static boolean isInstrumenting() {
         final ActivityThread activityThread = ActivityThread.currentActivityThread();
         if (activityThread == null) {
@@ -203,12 +230,9 @@
 
     private static final class MatchDeliverableMessages extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
-            if (m.when <= when) {
-                return true;
-            }
-            return false;
+            return n.mMessage.when <= when;
         }
     }
     private final MatchDeliverableMessages mMatchDeliverableMessages =
@@ -355,7 +379,7 @@
      * @see OnFileDescriptorEventListener
      * @see #removeOnFileDescriptorEventListener
      */
-    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+    @RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
     public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,
             @OnFileDescriptorEventListener.Events int events,
             @NonNull OnFileDescriptorEventListener listener) {
@@ -389,7 +413,7 @@
      * @see OnFileDescriptorEventListener
      * @see #addOnFileDescriptorEventListener
      */
-    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+    @RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
     public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) {
         if (fd == null) {
             throw new IllegalArgumentException("fd must not be null");
@@ -405,7 +429,7 @@
         }
     }
 
-    @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+    @RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
     private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,
             OnFileDescriptorEventListener listener) {
         final int fdNum = fd.getInt$();
@@ -528,7 +552,7 @@
     /* This is only read/written from the Looper thread. For use with Concurrent MQ */
     private int mNextPollTimeoutMillis;
     private boolean mMessageDirectlyQueued;
-    private Message nextMessage() {
+    private Message nextMessage(boolean peek) {
         int i = 0;
 
         while (true) {
@@ -690,7 +714,7 @@
             if (sState.compareAndSet(this, sStackStateActive, nextOp)) {
                 mMessageCounts.clearCounts();
                 if (found != null) {
-                    if (!removeFromPriorityQueue(found)) {
+                    if (!peek && !removeFromPriorityQueue(found)) {
                         /*
                          * RemoveMessages() might be able to pull messages out from under us
                          * However we can detect that here and just loop around if it happens.
@@ -724,7 +748,7 @@
             mMessageDirectlyQueued = false;
             nativePollOnce(ptr, mNextPollTimeoutMillis);
 
-            Message msg = nextMessage();
+            Message msg = nextMessage(false);
             if (msg != null) {
                 msg.markInUse();
                 return msg;
@@ -1043,8 +1067,9 @@
         }
 
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == null && m.arg1 == mBarrierToken) {
                 return true;
             }
@@ -1247,10 +1272,92 @@
         return true;
     }
 
+    private Message legacyPeekOrPop(boolean peek) {
+        synchronized (this) {
+            // Try to retrieve the next message.  Return if found.
+            final long now = SystemClock.uptimeMillis();
+            Message prevMsg = null;
+            Message msg = mMessages;
+            if (msg != null && msg.target == null) {
+                // Stalled by a barrier.  Find the next asynchronous message in the queue.
+                do {
+                    prevMsg = msg;
+                    msg = msg.next;
+                } while (msg != null && !msg.isAsynchronous());
+            }
+            if (msg != null) {
+                if (now >= msg.when) {
+                    // Got a message.
+                    mBlocked = false;
+                    if (peek) {
+                        return msg;
+                    }
+                    if (prevMsg != null) {
+                        prevMsg.next = msg.next;
+                        if (prevMsg.next == null) {
+                            mLast = prevMsg;
+                        }
+                    } else {
+                        mMessages = msg.next;
+                        if (msg.next == null) {
+                            mLast = null;
+                        }
+                    }
+                    msg.next = null;
+                    msg.markInUse();
+                    if (msg.isAsynchronous()) {
+                        mAsyncMessageCount--;
+                    }
+                    if (TRACE) {
+                        Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+                    }
+                    return msg;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the timestamp of the next executable message in our priority queue.
+     * Returns null if there are no messages ready for delivery.
+     *
+     * Caller must ensure that this doesn't race 'next' from the Looper thread.
+     */
+    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+    Long peekWhenForTest() {
+        throwIfNotTest();
+        Message ret;
+        if (mUseConcurrent) {
+            ret = nextMessage(true);
+        } else {
+            ret = legacyPeekOrPop(true);
+        }
+        return ret != null ? ret.when : null;
+    }
+
+    /**
+     * Return the next executable message in our priority queue.
+     * Returns null if there are no messages ready for delivery
+     *
+     * Caller must ensure that this doesn't race 'next' from the Looper thread.
+     */
+    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+    @Nullable
+    Message popForTest() {
+        throwIfNotTest();
+        if (mUseConcurrent) {
+            return nextMessage(false);
+        } else {
+            return legacyPeekOrPop(false);
+        }
+    }
+
     private static final class MatchHandlerWhatAndObject extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.what == what && (object == null || m.obj == object)) {
                 return true;
             }
@@ -1281,8 +1388,9 @@
 
     private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) {
                 return true;
             }
@@ -1314,8 +1422,9 @@
 
     private static final class MatchHandlerRunnableAndObject extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.callback == r && (object == null || m.obj == object)) {
                 return true;
             }
@@ -1348,12 +1457,9 @@
 
     private static final class MatchHandler extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
-            if (m.target == h) {
-                return true;
-            }
-            return false;
+            return n.mMessage.target == h;
         }
     }
     private final MatchHandler mMatchHandler = new MatchHandler();
@@ -1531,8 +1637,9 @@
 
     private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) {
                 return true;
             }
@@ -1594,8 +1701,9 @@
 
     private static final class MatchHandlerAndObject extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && (object == null || m.obj == object)) {
                 return true;
             }
@@ -1655,8 +1763,9 @@
 
     private static final class MatchHandlerAndObjectEquals extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && (object == null || object.equals(m.obj))) {
                 return true;
             }
@@ -1762,7 +1871,7 @@
 
     private static final class MatchAllMessages extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
             return true;
         }
@@ -1774,9 +1883,10 @@
 
     private static final class MatchAllFutureMessages extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
-            if (m.when > when) {
+            final Message m = n.mMessage;
+                    if (m.when > when) {
                 return true;
             }
             return false;
@@ -2482,7 +2592,7 @@
      * This class is used to find matches for hasMessages() and removeMessages()
      */
     private abstract static class MessageCompare {
-        public abstract boolean compareMessage(Message m, Handler h, int what, Object object,
+        public abstract boolean compareMessage(MessageNode n, Handler h, int what, Object object,
                 Runnable r, long when);
     }
 
@@ -2517,7 +2627,7 @@
         MessageNode p = (MessageNode) top;
 
         while (true) {
-            if (compare.compareMessage(p.mMessage, h, what, object, r, when)) {
+            if (compare.compareMessage(p, h, what, object, r, when)) {
                 found = true;
                 if (DEBUG) {
                     Log.d(TAG_C, "stackHasMessages node matches");
@@ -2562,7 +2672,7 @@
         while (iterator.hasNext()) {
             MessageNode msg = iterator.next();
 
-            if (compare.compareMessage(msg.mMessage, h, what, object, r, when)) {
+            if (compare.compareMessage(msg, h, what, object, r, when)) {
                 if (removeMatches) {
                     found = true;
                     if (queue.remove(msg)) {
diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
index c2a47d7..47778ed 100644
--- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
@@ -16,9 +16,12 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.app.Instrumentation;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.ravenwood.annotation.RavenwoodRedirect;
 import android.ravenwood.annotation.RavenwoodRedirectionClass;
@@ -364,6 +367,28 @@
         mPtr = nativeInit();
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static void throwIfNotTest() {
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        if (activityThread == null) {
+            // Only tests can reach here.
+            return;
+        }
+        final Instrumentation instrumentation = activityThread.getInstrumentation();
+        if (instrumentation == null) {
+            // Only tests can reach here.
+            return;
+        }
+        if (instrumentation.isInstrumenting()) {
+            return;
+        }
+        throw new IllegalStateException("Test-only API called not from a test!");
+    }
+
+    private static void throwIfNotTest$ravenwood() {
+        return;
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
@@ -384,8 +409,9 @@
 
     private static final class MatchDeliverableMessages extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.when <= when) {
                 return true;
             }
@@ -562,7 +588,7 @@
     private static final AtomicLong mMessagesDelivered = new AtomicLong();
     private boolean mMessageDirectlyQueued;
 
-    private Message nextMessage() {
+    private Message nextMessage(boolean peek) {
         int i = 0;
 
         while (true) {
@@ -724,7 +750,7 @@
             if (sState.compareAndSet(this, sStackStateActive, nextOp)) {
                 mMessageCounts.clearCounts();
                 if (found != null) {
-                    if (!removeFromPriorityQueue(found)) {
+                    if (!peek && !removeFromPriorityQueue(found)) {
                         /*
                          * RemoveMessages() might be able to pull messages out from under us
                          * However we can detect that here and just loop around if it happens.
@@ -993,8 +1019,9 @@
         }
 
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == null && m.arg1 == mBarrierToken) {
                 return true;
             }
@@ -1039,6 +1066,47 @@
         }
     }
 
+    private static final class MatchEarliestMessage extends MessageCompare {
+        MessageNode mEarliest = null;
+
+        @Override
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
+            if (mEarliest == null || mEarliest.mMessage.when > m.when) {
+                mEarliest = n;
+            }
+
+            return false;
+        }
+    }
+
+    /**
+     * Get the timestamp of the next executable message in our priority queue.
+     * Returns null if there are no messages ready for delivery.
+     *
+     * Caller must ensure that this doesn't race 'next' from the Looper thread.
+     */
+    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+    Long peekWhenForTest() {
+        throwIfNotTest();
+        Message ret = nextMessage(true);
+        return ret != null ? ret.when : null;
+    }
+
+    /**
+     * Return the next executable message in our priority queue.
+     * Returns null if there are no messages ready for delivery
+     *
+     * Caller must ensure that this doesn't race 'next' from the Looper thread.
+     */
+    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+    @Nullable
+    Message popForTest() {
+        throwIfNotTest();
+        return nextMessage(false);
+    }
+
     private StateNode getStateNode(StackNode node) {
         if (node.isMessageNode()) {
             return ((MessageNode) node).mBottomOfStack;
@@ -1058,7 +1126,7 @@
      * This class is used to find matches for hasMessages() and removeMessages()
      */
     private abstract static class MessageCompare {
-        public abstract boolean compareMessage(Message m, Handler h, int what, Object object,
+        public abstract boolean compareMessage(MessageNode n, Handler h, int what, Object object,
                 Runnable r, long when);
     }
 
@@ -1167,8 +1235,9 @@
 
     private static final class MatchHandlerWhatAndObject extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.what == what && (object == null || m.obj == object)) {
                 return true;
             }
@@ -1187,8 +1256,9 @@
 
     private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r,
                 long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) {
                 return true;
             }
@@ -1208,8 +1278,9 @@
 
     private static final class MatchHandlerRunnableAndObject extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.callback == r && (object == null || m.obj == object)) {
                 return true;
             }
@@ -1229,8 +1300,9 @@
 
     private static final class MatchHandler extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == h) {
                 return true;
             }
@@ -1268,8 +1340,9 @@
 
     private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) {
                 return true;
             }
@@ -1287,8 +1360,9 @@
 
     private static final class MatchHandlerAndObject extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == h && (object == null || m.obj == object)) {
                 return true;
             }
@@ -1305,8 +1379,9 @@
 
     private static final class MatchHandlerAndObjectEquals extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.target == h && (object == null || object.equals(m.obj))) {
                 return true;
             }
@@ -1324,8 +1399,8 @@
 
     private static final class MatchAllMessages extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
             return true;
         }
     }
@@ -1336,8 +1411,9 @@
 
     private static final class MatchAllFutureMessages extends MessageCompare {
         @Override
-        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
-                long when) {
+        public boolean compareMessage(MessageNode n, Handler h, int what, Object object,
+                Runnable r, long when) {
+            final Message m = n.mMessage;
             if (m.when > when) {
                 return true;
             }
diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
index cae82d0..f49acd1 100644
--- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java
+++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
@@ -16,9 +16,14 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
+import android.app.ActivityThread;
+import android.app.Instrumentation;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.ravenwood.annotation.RavenwoodRedirect;
@@ -99,6 +104,28 @@
         mPtr = nativeInit();
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static void throwIfNotTest() {
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        if (activityThread == null) {
+            // Only tests can reach here.
+            return;
+        }
+        final Instrumentation instrumentation = activityThread.getInstrumentation();
+        if (instrumentation == null) {
+            // Only tests can reach here.
+            return;
+        }
+        if (instrumentation.isInstrumenting()) {
+            return;
+        }
+        throw new IllegalStateException("Test-only API called not from a test!");
+    }
+
+    private static void throwIfNotTest$ravenwood() {
+        return;
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
@@ -713,6 +740,78 @@
         return true;
     }
 
+    private Message legacyPeekOrPop(boolean peek) {
+        synchronized (this) {
+            // Try to retrieve the next message.  Return if found.
+            final long now = SystemClock.uptimeMillis();
+            Message prevMsg = null;
+            Message msg = mMessages;
+            if (msg != null && msg.target == null) {
+                // Stalled by a barrier.  Find the next asynchronous message in the queue.
+                do {
+                    prevMsg = msg;
+                    msg = msg.next;
+                } while (msg != null && !msg.isAsynchronous());
+            }
+            if (msg != null) {
+                if (now >= msg.when) {
+                    // Got a message.
+                    mBlocked = false;
+                    if (peek) {
+                        return msg;
+                    }
+                    if (prevMsg != null) {
+                        prevMsg.next = msg.next;
+                        if (prevMsg.next == null) {
+                            mLast = prevMsg;
+                        }
+                    } else {
+                        mMessages = msg.next;
+                        if (msg.next == null) {
+                            mLast = null;
+                        }
+                    }
+                    msg.next = null;
+                    msg.markInUse();
+                    if (msg.isAsynchronous()) {
+                        mAsyncMessageCount--;
+                    }
+                    if (TRACE) {
+                        Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+                    }
+                    return msg;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the timestamp of the next executable message in our priority queue.
+     * Returns null if there are no messages ready for delivery.
+     *
+     * Caller must ensure that this doesn't race 'next' from the Looper thread.
+     */
+    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+    Long peekWhenForTest() {
+        throwIfNotTest();
+        Message ret = legacyPeekOrPop(true);
+        return ret != null ? ret.when : null;
+    }
+
+    /**
+     * Return the next executable message in our priority queue.
+     * Returns null if there are no messages ready for delivery
+     *
+     * Caller must ensure that this doesn't race 'next' from the Looper thread.
+     */
+    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
+    @Nullable
+    Message popForTest() {
+        throwIfNotTest();
+        return legacyPeekOrPop(false);
+    }
+
     boolean hasMessages(Handler h, int what, Object object) {
         if (h == null) {
             return false;