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

Commit 6de9be16 authored by Jeff DeCew's avatar Jeff DeCew
Browse files

Create AnimatorTestRule2 which avoids the test interaction bugs with the...

Create AnimatorTestRule2 which avoids the test interaction bugs with the original as it currently exists

Bug: 269085199
Bug: 275602127
Test: atest SystemStatusAnimationSchedulerImplTest NotificationWakeUpCoordinatorTest
Change-Id: Ib28b96ea7e19a57bf21ef66f6d7dd1e563a200ad
parent 319f4d77
Loading
Loading
Loading
Loading
+174 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.core.animation;

import android.os.Looper;
import android.os.SystemClock;
import android.util.AndroidRuntimeException;

import androidx.annotation.NonNull;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

import java.util.ArrayList;
import java.util.List;

/**
 * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to
 * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static
 * list of callbacks.
 *
 * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code.
 */
public final class AnimatorTestRule2 implements TestRule {

    class TestAnimationHandler extends AnimationHandler {
        TestAnimationHandler() {
            super(new TestProvider());
        }

        List<AnimationFrameCallback> animationCallbacks = new ArrayList<>();

        @Override
        void addAnimationFrameCallback(AnimationFrameCallback callback) {
            animationCallbacks.add(callback);
            callback.doAnimationFrame(getCurrentTime());
        }

        @Override
        public void removeCallback(AnimationFrameCallback callback) {
            int id = animationCallbacks.indexOf(callback);
            if (id >= 0) {
                animationCallbacks.set(id, null);
            }
        }

        void onAnimationFrame(long frameTime) {
            for (int i = 0; i < animationCallbacks.size(); i++) {
                final AnimationFrameCallback callback = animationCallbacks.get(i);
                if (callback == null) {
                    continue;
                }
                callback.doAnimationFrame(frameTime);
            }
        }

        @Override
        void autoCancelBasedOn(ObjectAnimator objectAnimator) {
            for (int i = animationCallbacks.size() - 1; i >= 0; i--) {
                AnimationFrameCallback cb = animationCallbacks.get(i);
                if (cb == null) {
                    continue;
                }
                if (objectAnimator.shouldAutoCancel(cb)) {
                    ((Animator) animationCallbacks.get(i)).cancel();
                }
            }
        }
    }

    final TestAnimationHandler mTestHandler;
    final long mStartTime;
    private long mTotalTimeDelta = 0;
    private final Object mLock = new Object();

    public AnimatorTestRule2() {
        mStartTime = SystemClock.uptimeMillis();
        mTestHandler = new TestAnimationHandler();
    }

    @NonNull
    @Override
    public Statement apply(@NonNull final Statement base, @NonNull Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                AnimationHandler.setTestHandler(mTestHandler);
                try {
                    base.evaluate();
                } finally {
                    AnimationHandler.setTestHandler(null);
                }
            }
        };
    }

    /**
     * Advances the animation clock by the given amount of delta in milliseconds. This call will
     * produce an animation frame to all the ongoing animations. This method needs to be
     * called on the same thread as {@link Animator#start()}.
     *
     * @param timeDelta the amount of milliseconds to advance
     */
    public void advanceTimeBy(long timeDelta) {
        if (Looper.myLooper() == null) {
            // Throw an exception
            throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be"
                    + "called on Looper threads");
        }
        synchronized (mLock) {
            // Advance time & pulse a frame
            mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta;
        }
        // produce a frame
        mTestHandler.onAnimationFrame(getCurrentTime());
    }


    /**
     * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
     * different time than the time tracked by {@link SystemClock} This method needs to be called on
     * the same thread as {@link Animator#start()}.
     */
    public long getCurrentTime() {
        if (Looper.myLooper() == null) {
            // Throw an exception
            throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be"
                    + "called on Looper threads");
        }
        synchronized (mLock) {
            return mStartTime + mTotalTimeDelta;
        }
    }


    private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
        TestProvider() {
        }

        @Override
        public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) {
            callback.doAnimationFrame(getCurrentTime());
        }

        @Override
        public void postFrameCallback() {
        }

        @Override
        public void setFrameDelay(long delay) {
        }

        @Override
        public long getFrameDelay() {
            return 0;
        }
    }
}
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.core.animation

import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.doOnEnd
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
class AnimatorTestRuleTest : SysuiTestCase() {

    @get:Rule val animatorTestRule = AnimatorTestRule2()

    @Test
    fun testA() {
        didTouchA = false
        didTouchB = false
        ObjectAnimator.ofFloat(0f, 1f).apply {
            duration = 100
            doOnEnd { didTouchA = true }
            start()
        }
        ObjectAnimator.ofFloat(0f, 1f).apply {
            duration = 150
            doOnEnd { didTouchA = true }
            start()
        }
        animatorTestRule.advanceTimeBy(100)
        assertThat(didTouchA).isTrue()
        assertThat(didTouchB).isFalse()
    }

    @Test
    fun testB() {
        didTouchA = false
        didTouchB = false
        ObjectAnimator.ofFloat(0f, 1f).apply {
            duration = 100
            doOnEnd { didTouchB = true }
            start()
        }
        ObjectAnimator.ofFloat(0f, 1f).apply {
            duration = 150
            doOnEnd { didTouchB = true }
            start()
        }
        animatorTestRule.advanceTimeBy(100)
        assertThat(didTouchA).isFalse()
        assertThat(didTouchB).isTrue()
    }

    companion object {
        var didTouchA = false
        var didTouchB = false
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import android.widget.FrameLayout
import androidx.core.animation.AnimatorTestRule
import androidx.core.animation.AnimatorTestRule2
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -70,7 +70,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
    private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
    private val fakeFeatureFlags = FakeFeatureFlags()

    @get:Rule val animatorTestRule = AnimatorTestRule()
    @get:Rule val animatorTestRule = AnimatorTestRule2()

    @Before
    fun setup() {
+2 −2
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification

import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.core.animation.AnimatorTestRule
import androidx.core.animation.AnimatorTestRule2
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -51,7 +51,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class NotificationWakeUpCoordinatorTest : SysuiTestCase() {

    @get:Rule val animatorTestRule = AnimatorTestRule()
    @get:Rule val animatorTestRule = AnimatorTestRule2()

    private val dumpManager: DumpManager = mock()
    private val headsUpManager: HeadsUpManager = mock()
+2 −2
Original line number Diff line number Diff line
@@ -62,7 +62,7 @@ import android.window.OnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;

import androidx.annotation.NonNull;
import androidx.core.animation.AnimatorTestRule;
import androidx.core.animation.AnimatorTestRule2;
import androidx.test.filters.SmallTest;

import com.android.internal.logging.UiEventLogger;
@@ -110,7 +110,7 @@ public class RemoteInputViewTest extends SysuiTestCase {
    private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();

    @ClassRule
    public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
    public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2();

    @Before
    public void setUp() throws Exception {