diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt index 7242cb20dc77bdd1320910e307cf8c8234577510..f6c05669896783188025bb5c4c09c28c76419d8b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt @@ -54,7 +54,7 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { private lateinit var powerInteractor: PowerInteractor private lateinit var underTest: LightRevealScrimRepositoryImpl - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) @Before fun setUp() { diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt b/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt index 0fe2283c85431d732c00a8757ed65c72f0bffc96..f23fbee18904bfb65db90f76f2ebfed3701041e6 100644 --- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt +++ b/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt @@ -34,7 +34,7 @@ import org.junit.runner.RunWith @RunWithLooper class AnimatorTestRuleIsolationTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) @Test fun testA() { diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt b/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt index cc7f7e4067d9368336d1bef52dad999e41573ed0..fd5f1573ba5eb139133c657fd841f608f1e3724e 100644 --- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt +++ b/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt @@ -31,7 +31,7 @@ import org.junit.runner.RunWith @RunWithLooper class AnimatorTestRulePrecisionTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) var value1: Float = -1f var value2: Float = -1f diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index fad85526dec9676f0af648797bb484991f068ccb..e893eb196039f43d5c44bc4df4f862e88ef29f10 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -56,7 +56,7 @@ import java.lang.reflect.Field; public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControllerBaseTest { @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @Test public void dozeTimeTick_updatesSlice() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java index ba27fcd49fac4c0552a185eb3da2210022ec0abc..dd428f562bd1d5700863012dedb68c3480f07523 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java @@ -48,7 +48,7 @@ import org.junit.runner.RunWith; public class ExpandHelperTest extends SysuiTestCase { @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private ExpandableNotificationRow mRow; diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java index 8299acbc2d52e39ffeccbd1c0f3c94416cd51c34..b54c996fbffdfea5431bc6ba73984f290d93eeda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -82,7 +82,7 @@ import java.util.function.Supplier; public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final float DEFAULT_SCALE = 4.0f; diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 2225ad6e49d766255b6dd1c7d8b75e67cd5ea967..f1b0c18745117bf57e74f4aaa648bb2ef3e63f0e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -129,7 +129,8 @@ import java.util.concurrent.atomic.AtomicInteger; public class WindowMagnificationControllerTest extends SysuiTestCase { @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + // NOTE: pass 'null' to allow this test advances time on the main thread. + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(null); @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java index 66fb63b6c3311cd73576b73b248e9da85fcfe7af..d58b6ddd4495068e1a4fc60b33ef14e90a51c5c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java @@ -132,7 +132,7 @@ import java.util.function.Supplier; public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase { @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt index 2b51ac5e31873de9a91bcdf990cdccc25ef2c526..e3be3822fb94058d02b15d55743db866df1c0502 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt @@ -34,7 +34,7 @@ import org.junit.runner.RunWith @FlakyTest(bugId = 302149604) class AnimatorTestRuleOrderTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) var value1: Float = -1f var value2: Float = -1f diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt index 5b29a867ceaf53d725a57467069c82a493771d0b..7787a7fa6af1ac862e3c6ffe5db9d30659d58377 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt @@ -43,7 +43,7 @@ import org.mockito.MockitoAnnotations @RunWithLooper(setAsMainLooper = true) @kotlinx.coroutines.ExperimentalCoroutinesApi class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) private lateinit var underTest: KeyguardSurfaceBehindParamsApplier private lateinit var executor: FakeExecutor diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt index e8aa8f0bdc5da4f3e96972be117254729dad90e5..56d97ddb3b48e4b93b3aa9bc538f5f87e77858aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt @@ -40,7 +40,7 @@ import org.junit.runner.RunWith @SmallTest class QSIconViewImplTest_311121830 : SysuiTestCase() { - @get:Rule val animatorRule = AnimatorTestRule() + @get:Rule val animatorRule = AnimatorTestRule(this) @Test fun alwaysLastIcon() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt index 8be2ef008039fec168aae5549004b226b70b4b33..452302d4db8ae83d11b4d9e47d383002bf41dc98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt @@ -51,7 +51,7 @@ import org.mockito.MockitoAnnotations class SystemEventChipAnimationControllerTest : SysuiTestCase() { private lateinit var controller: SystemEventChipAnimationController - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) @Mock private lateinit var sbWindowController: StatusBarWindowController @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index 875fe58e5b50b8edcf0c4f10ff75ed6e4a83f378..cacfa8dc0be44dcbdc75c97b91ff506e97755d89 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -76,7 +76,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { private lateinit var chipAnimationController: SystemEventChipAnimationController private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) @Before fun setup() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index 039fef9c1df51f8fff761165785f6b480660aed3..82093adcb75cb14e212c65a84791b2fb9fc75b63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -54,7 +54,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions @TestableLooper.RunWithLooper(setAsMainLooper = true) class NotificationWakeUpCoordinatorTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) private val kosmos = Kosmos() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 54d3607c7a83ba2b8338d699f0420c50c5cd3ac3..3da5ab9f9d3d99de589bc9bd95446cbdb06de9b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -131,7 +131,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt index 2ce060c5d097eb59036f4da306b8ba2cdbfc7170..997c00cf49a482d984dcafbc75efb483a784015b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt @@ -42,7 +42,7 @@ class MultiSourceMinAlphaControllerTest : SysuiTestCase() { private val multiSourceMinAlphaController = MultiSourceMinAlphaController(view, initialAlpha = INITIAL_ALPHA) - @get:Rule val animatorTestRule = AnimatorTestRule() + @get:Rule val animatorTestRule = AnimatorTestRule(this) @Before fun setup() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 658e6b01d7b4d1e61d6e8c93abeb777a3f20b9d0..13167b2d281fef07707911354589c9a079ec4ceb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -112,7 +112,7 @@ public class RemoteInputViewTest extends SysuiTestCase { private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake(); @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @Before public void setUp() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 7a8dce8fcd9071fdb49f50b71e645cedbb27dfd1..629c6a80f2eb915bb73c3854393cbccb71604631 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -160,7 +160,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { private FakeSettings mSecureSettings; @Rule - public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @Before public void setup() throws Exception { diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java index 1afe56f27be7b62638924017e63acf91f309a46e..5860c2dd4cdcaa592d6f865b2c750af138a15770 100644 --- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java +++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java @@ -16,14 +16,21 @@ package android.animation; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import android.animation.AnimationHandler.AnimationFrameCallback; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Looper; import android.os.SystemClock; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunnableWithException; import android.util.AndroidRuntimeException; import android.util.Singleton; import android.view.Choreographer; +import android.view.animation.AnimationUtils; + +import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.util.Preconditions; @@ -74,25 +81,31 @@ public final class AnimatorTestRule implements TestRule { return new TestHandler(); } }; + private final Object mTest; private final long mStartTime; private long mTotalTimeDelta = 0; + private volatile boolean mCanLockAnimationClock; + private Looper mLooperWithLockedAnimationClock; /** - * Construct an AnimatorTestRule with a custom start time. - * @see #AnimatorTestRule() + * Construct an AnimatorTestRule with access to the test instance and a custom start time. + * @see #AnimatorTestRule(Object) */ - public AnimatorTestRule(long startTime) { + public AnimatorTestRule(Object test, long startTime) { + mTest = test; mStartTime = startTime; } /** - * Construct an AnimatorTestRule with a start time of {@link SystemClock#uptimeMillis()}. - * Initializing the start time with this clock reduces the discrepancies with various internals - * of classes like ValueAnimator which can sometimes read that clock via - * {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}. + * Construct an AnimatorTestRule for the given test instance with a start time of + * {@link SystemClock#uptimeMillis()}. Initializing the start time with this clock reduces the + * discrepancies with various internals of classes like ValueAnimator which can sometimes read + * that clock via {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}. + * + * @param test the test instance used to access the {@link TestableLooper} used by the class. */ - public AnimatorTestRule() { - this(SystemClock.uptimeMillis()); + public AnimatorTestRule(Object test) { + this(test, SystemClock.uptimeMillis()); } @NonNull @@ -102,10 +115,16 @@ public final class AnimatorTestRule implements TestRule { @Override public void evaluate() throws Throwable { final TestHandler testHandler = mTestHandler.get(); - AnimationHandler objAtStart = AnimationHandler.setTestHandler(testHandler); + final AnimationHandler objAtStart = AnimationHandler.setTestHandler(testHandler); + final RunnableWithException lockClock = + wrapWithRunBlocking(new LockAnimationClockRunnable()); + final RunnableWithException unlockClock = + wrapWithRunBlocking(new UnlockAnimationClockRunnable()); try { + lockClock.run(); base.evaluate(); } finally { + unlockClock.run(); AnimationHandler objAtEnd = AnimationHandler.setTestHandler(objAtStart); if (testHandler != objAtEnd) { // pass or fail, inner logic not restoring the handler needs to be reported. @@ -118,6 +137,78 @@ public final class AnimatorTestRule implements TestRule { }; } + private RunnableWithException wrapWithRunBlocking(RunnableWithException runnable) { + RunnableWithException wrapped = TestableLooper.wrapWithRunBlocking(mTest, runnable); + if (wrapped != null) { + return wrapped; + } + return () -> runOnMainThrowing(runnable); + } + + private static void runOnMainThrowing(RunnableWithException runnable) throws Exception { + if (Looper.myLooper() == Looper.getMainLooper()) { + runnable.run(); + } else { + final Throwable[] throwableBox = new Throwable[1]; + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + try { + runnable.run(); + } catch (Throwable t) { + throwableBox[0] = t; + } + }); + if (throwableBox[0] == null) { + return; + } else if (throwableBox[0] instanceof RuntimeException ex) { + throw ex; + } else if (throwableBox[0] instanceof Error err) { + throw err; + } else { + throw new RuntimeException(throwableBox[0]); + } + } + } + + private class LockAnimationClockRunnable implements RunnableWithException { + @Override + public void run() { + mLooperWithLockedAnimationClock = Looper.myLooper(); + mCanLockAnimationClock = true; + lockAnimationClockToCurrentTime(); + } + } + + private class UnlockAnimationClockRunnable implements RunnableWithException { + @Override + public void run() { + mCanLockAnimationClock = false; + mLooperWithLockedAnimationClock = null; + AnimationUtils.unlockAnimationClock(); + } + } + + private void lockAnimationClockToCurrentTime() { + if (!mCanLockAnimationClock) { + throw new AssertionError("Unable to lock the animation clock; " + + "has the test started? already finished?"); + } + if (mLooperWithLockedAnimationClock != Looper.myLooper()) { + throw new AssertionError("Animation clock being locked on " + Looper.myLooper() + + " but should only be locked on " + mLooperWithLockedAnimationClock); + } + long desiredTime = getCurrentTime(); + AnimationUtils.lockAnimationClock(desiredTime); + if (!mCanLockAnimationClock) { + AnimationUtils.unlockAnimationClock(); + throw new AssertionError("Threading error when locking the animation clock"); + } + long outputTime = AnimationUtils.currentAnimationTimeMillis(); + if (outputTime != desiredTime) { + throw new AssertionError("currentAnimationTimeMillis() is " + outputTime + + " after locking to " + desiredTime); + } + } + /** * If any new {@link Animator}s have been registered since the last time the frame time was * advanced, initialize them with the current frame time. Failing to do this will result in the @@ -178,6 +269,7 @@ public final class AnimatorTestRule implements TestRule { // advance time mTotalTimeDelta += timeDelta; } + lockAnimationClockToCurrentTime(); if (preFrameAction != null) { preFrameAction.accept(timeDelta); // After letting other code run, clear any new callbacks to avoid double-ticking them diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt index ba9c5eda1b635fa7f8f4f7a815f721cbf93189e2..e2fc44fd2d0d6b244b0b65b2a69f1ef2d0a41cc3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt @@ -26,12 +26,16 @@ import org.junit.runners.model.Statement * A rule that wraps both [androidx.core.animation.AnimatorTestRule] and * [android.animation.AnimatorTestRule] such that the clocks of the two animation handlers can be * advanced together. + * + * @param test the instance of the test used to look up the TestableLooper. If a TestableLooper is + * found, the time can only be advanced on that thread; otherwise the time must be advanced on the + * main thread. */ -class AnimatorTestRule : TestRule { +class AnimatorTestRule(test: Any?) : TestRule { // Create the androidx rule, which initializes start time to SystemClock.uptimeMillis(), // then copy that time to the platform rule so that the two clocks are in sync. private val androidxRule = androidx.core.animation.AnimatorTestRule() - private val platformRule = android.animation.AnimatorTestRule(androidxRule.startTime) + private val platformRule = android.animation.AnimatorTestRule(test, androidxRule.startTime) private val advanceAndroidXTimeBy = Consumer<Long> { timeDelta -> androidxRule.advanceTimeBy(timeDelta) } diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index 60c25b75d2e9d705d5b207f229c5ec41c2dff906..be5c84c0353c17366425bcf32b2c86f020bc5cbc 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -14,6 +14,8 @@ package android.testing; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -85,6 +87,57 @@ public class TestableLooper { setupQueue(looper); } + /** + * Wrap the given runnable so that it will run blocking on the Looper that will be set up for + * the given test. + * <p> + * This method is required to support any TestRule which needs to run setup and/or teardown code + * on the TestableLooper. Whether using {@link AndroidTestingRunner} or + * {@link TestWithLooperRule}, the TestRule's Statement evaluates on the test instrumentation + * thread, rather than the TestableLooper thread, so access to the TestableLooper is required. + * However, {@link #get(Object)} will return {@code null} both before and after the inner + * statement is evaluated: + * <ul> + * <li>Before the test {@link #get} returns {@code null} because while the TestableLooperHolder + * is accessible in sLoopers, it has not been initialized with an actual TestableLooper yet. + * This method's use of the internal LooperFrameworkMethod ensures that all setup and teardown + * of the TestableLooper happen as it would for all other wrapped code blocks. + * <li>After the test {@link #get} can return {@code null} because many tests call + * {@link #remove} in the teardown method. The fact that this method returns a runnable allows + * it to be called before the test (when the TestableLooperHolder is still in sLoopers), and + * then executed as teardown after the test. + * </ul> + * + * @param test the test instance (just like passed to {@link #get(Object)}) + * @param runnable the operation that should eventually be run on the TestableLooper + * @return a runnable that will block the thread on which it is called until the given runnable + * is finished. Will be {@code null} if there is no looper for the given test. + * @hide + */ + @Nullable + public static RunnableWithException wrapWithRunBlocking( + Object test, @NonNull RunnableWithException runnable) { + TestableLooperHolder looperHolder = sLoopers.get(test); + if (looperHolder == null) { + return null; + } + try { + FrameworkMethod base = new FrameworkMethod(runnable.getClass().getMethod("run")); + LooperFrameworkMethod wrapped = new LooperFrameworkMethod(base, looperHolder); + return () -> { + try { + wrapped.invokeExplosively(runnable); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException(e); + } + }; + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + public Looper getLooper() { return mLooper; }