Merge "Platform AnimatorTestRule uses lockAnimationClock to ensure determinism." into main
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 7242cb2..f6c0566 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 @@
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 0fe2283..f23fbee 100644
--- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
+++ b/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
@@ -34,7 +34,7 @@
@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 cc7f7e4..fd5f157 100644
--- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
+++ b/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
@@ -31,7 +31,7 @@
@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 fad8552..e893eb1 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 @@
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 ba27fcd..dd428f5 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 @@
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 e006d59..64936862 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 @@
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 2225ad6..f1b0c18 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 @@
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 a35a509..08b49e7 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 @@
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 2b51ac5..e3be3822 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 @@
@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 5b29a86..7787a7f 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 @@
@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 bbae0c9..ae2a9ad 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 @@
@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 8be2ef0..452302d 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 @@
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 875fe58..cacfa8d 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 @@
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 039fef9..82093ad 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 @@
@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 54d3607..3da5ab9 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 @@
@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 2ce060c..997c00c 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 @@
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 658e6b0..13167b2 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 @@
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 8a33778..25a0bc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -157,7 +157,7 @@
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 1afe56f..5860c2d 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 @@
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 @@
@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 @@
};
}
+ 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 @@
// 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 ba9c5ed..e2fc44f 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 @@
* 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 60c25b7..be5c84c 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 @@
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;
}