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

Commit 8c70c30f authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Reland "Remove MessageQueue reflection from TestableLooper"" into main

parents 12267232 2af8843e
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 = () -> { };