Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2af8843e authored by Shai Barack's avatar Shai Barack
Browse files

Reland "Remove MessageQueue reflection from TestableLooper"

This reverts commit 83dd77bb.

Reason for revert: fixed underlying issue, reran failures from b/395717488

Change-Id: I6da2f25989a887ddaa1ca5dd7ea2032e060a61ba
parent 83dd77bb
Loading
Loading
Loading
Loading
+90 −22
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ package android.testing;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -24,7 +25,7 @@ import android.os.MessageQueue;
import android.os.TestLooperManager;
import android.util.ArrayMap;

import androidx.test.InstrumentationRegistry;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.runners.model.FrameworkMethod;

@@ -33,8 +34,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;

/**
@@ -67,7 +71,28 @@ public class TestableLooper {
    private Handler mHandler;
    private TestLooperManager mQueueWrapper;

    /**
     * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
     */
    private static boolean isAtLeastBaklava() {
        Method[] methods = TestLooperManager.class.getMethods();
        for (Method method : methods) {
            if (method.getName().equals("peekWhen")) {
                return true;
            }
        }
        return false;
        // TODO(shayba): delete the above, uncomment the below.
        // SDK_INT has not yet ramped to Baklava in all 25Q2 builds.
        // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
    }

    static {
        if (isAtLeastBaklava()) {
            MESSAGE_QUEUE_MESSAGES_FIELD = null;
            MESSAGE_NEXT_FIELD = null;
            MESSAGE_WHEN_FIELD = null;
        } else {
            try {
                MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
                MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
@@ -79,6 +104,7 @@ public class TestableLooper {
                throw new RuntimeException("Failed to initialize TestableLooper", e);
            }
        }
    }

    public TestableLooper(Looper l) throws Exception {
        this(acquireLooperManager(l), l);
@@ -222,8 +248,61 @@ public class TestableLooper {
    }

    public void moveTimeForward(long milliSeconds) {
        if (isAtLeastBaklava()) {
            moveTimeForwardBaklava(milliSeconds);
        } else {
            moveTimeForwardLegacy(milliSeconds);
        }
    }

    private void moveTimeForwardBaklava(long milliSeconds) {
        // Drain all Messages from the queue.
        Queue<Message> messages = new ArrayDeque<>();
        while (true) {
            Message message = mQueueWrapper.poll();
            if (message == null) {
                break;
            }

            // Adjust the Message's delivery time.
            long newWhen = message.when - milliSeconds;
            if (newWhen < 0) {
                newWhen = 0;
            }
            message.when = newWhen;
            messages.add(message);
        }

        // Repost all Messages back to the queuewith a new time.
        while (true) {
            Message message = messages.poll();
            if (message == null) {
                break;
            }

            Runnable callback = message.getCallback();
            Handler handler = message.getTarget();
            long when = message.getWhen();

            // The Message cannot be re-enqueued because it is marked in use.
            // Make a copy of the Message and recycle the original.
            // This resets {@link Message#isInUse()} but retains all other content.
            {
                Message newMessage = Message.obtain();
                newMessage.copyFrom(message);
                newMessage.setCallback(callback);
                mQueueWrapper.recycle(message);
                message = newMessage;
            }

            // Send the Message back to its Handler to be re-enqueued.
            handler.sendMessageAtTime(message, when);
        }
    }

    private void moveTimeForwardLegacy(long milliSeconds) {
        try {
            Message msg = getMessageLinkedList();
            Message msg = (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(mLooper.getQueue());
            while (msg != null) {
                long updatedWhen = msg.getWhen() - milliSeconds;
                if (updatedWhen < 0) {
@@ -237,17 +316,6 @@ public class TestableLooper {
        }
    }

    private Message getMessageLinkedList() {
        try {
            MessageQueue queue = mLooper.getQueue();
            return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(
                    "Access failed in TestableLooper: get - MessageQueue.mMessages",
                    e);
        }
    }

    private int processQueuedMessages() {
        int count = 0;
        Runnable barrierRunnable = () -> { };