Merge "Fix flaky tests in ExpandedAnimationControllerTest" into main
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 5b0239f..02af2d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -563,7 +563,7 @@
? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
: p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
animationForChild(child)
- .translationX(fromX, p.y)
+ .translationX(fromX, p.x)
.start();
} else {
float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
@@ -634,4 +634,9 @@
.start();
}
}
+
+ /** Returns true if we're in the middle of a collapse or expand animation. */
+ boolean isAnimating() {
+ return mAnimatingCollapse || mAnimatingExpand;
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index c1ff260..60f1d271 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -16,52 +16,51 @@
package com.android.wm.shell.bubbles.animation;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.SuppressLint;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleStackView;
+import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
- private int mDisplayWidth = 500;
- private int mDisplayHeight = 1000;
-
- private Runnable mOnBubbleAnimatedOutAction = mock(Runnable.class);
+ private final Semaphore mBubbleRemovedSemaphore = new Semaphore(0);
+ private final Runnable mOnBubbleAnimatedOutAction = mBubbleRemovedSemaphore::release;
ExpandedAnimationController mExpandedController;
private int mStackOffset;
private PointF mExpansionPoint;
private BubblePositioner mPositioner;
- private BubbleStackView.StackViewState mStackViewState = new BubbleStackView.StackViewState();
+ private final BubbleStackView.StackViewState mStackViewState =
+ new BubbleStackView.StackViewState();
- @SuppressLint("VisibleForTests")
@Before
public void setUp() throws Exception {
super.setUp();
@@ -70,15 +69,13 @@
getContext().getSystemService(WindowManager.class));
mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
Insets.of(0, 0, 0, 0),
- new Rect(0, 0, mDisplayWidth, mDisplayHeight));
+ new Rect(0, 0, 500, 1000));
BubbleStackView stackView = mock(BubbleStackView.class);
- when(stackView.getState()).thenReturn(getStackViewState());
mExpandedController = new ExpandedAnimationController(mPositioner,
mOnBubbleAnimatedOutAction,
stackView);
- spyOn(mExpandedController);
addOneMoreThanBubbleLimitBubbles();
mLayout.setActiveController(mExpandedController);
@@ -86,9 +83,18 @@
Resources res = mLayout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mExpansionPoint = new PointF(100, 100);
+
+ getStackViewState();
+ when(stackView.getState()).thenAnswer(i -> getStackViewState());
+ waitForMainThread();
}
- public BubbleStackView.StackViewState getStackViewState() {
+ @After
+ public void tearDown() {
+ waitForMainThread();
+ }
+
+ private BubbleStackView.StackViewState getStackViewState() {
mStackViewState.numberOfBubbles = mLayout.getChildCount();
mStackViewState.selectedIndex = 0;
mStackViewState.onLeft = mPositioner.isStackOnLeft(mExpansionPoint);
@@ -96,68 +102,71 @@
}
@Test
- @Ignore
- public void testExpansionAndCollapse() throws InterruptedException {
- Runnable afterExpand = mock(Runnable.class);
- mExpandedController.expandFromStack(afterExpand);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
+ public void testExpansionAndCollapse() throws Exception {
+ expand();
testBubblesInCorrectExpandedPositions();
- verify(afterExpand).run();
+ waitForMainThread();
- Runnable afterCollapse = mock(Runnable.class);
+ final Semaphore semaphore = new Semaphore(0);
+ Runnable afterCollapse = semaphore::release;
mExpandedController.collapseBackToStack(mExpansionPoint, false, afterCollapse);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
- testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1);
- verify(afterExpand).run();
+ assertThat(semaphore.tryAcquire(1, 2, TimeUnit.SECONDS)).isTrue();
+ waitForAnimation();
+ testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y);
}
@Test
- @Ignore
- public void testOnChildAdded() throws InterruptedException {
+ public void testOnChildAdded() throws Exception {
expand();
+ waitForMainThread();
// Add another new view and wait for its animation.
final View newView = new FrameLayout(getContext());
mLayout.addView(newView, 0);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
}
@Test
- @Ignore
- public void testOnChildRemoved() throws InterruptedException {
+ public void testOnChildRemoved() throws Exception {
expand();
+ waitForMainThread();
- // Remove some views and see if the remaining child views still pass the expansion test.
+ // Remove some views and verify the remaining child views still pass the expansion test.
mLayout.removeView(mViews.get(0));
mLayout.removeView(mViews.get(3));
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ // Removing a view will invoke onBubbleAnimatedOutAction. Block until it gets called twice.
+ assertThat(mBubbleRemovedSemaphore.tryAcquire(2, 2, TimeUnit.SECONDS)).isTrue();
+
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
}
@Test
- public void testDragBubbleOutDoesntNPE() throws InterruptedException {
+ public void testDragBubbleOutDoesntNPE() {
mExpandedController.onGestureFinished();
mExpandedController.dragBubbleOut(mViews.get(0), 1, 1);
}
/** Expand the stack and wait for animations to finish. */
private void expand() throws InterruptedException {
- mExpandedController.expandFromStack(mock(Runnable.class));
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ final Semaphore semaphore = new Semaphore(0);
+ Runnable afterExpand = semaphore::release;
+
+ mExpandedController.expandFromStack(afterExpand);
+ assertThat(semaphore.tryAcquire(1, TimeUnit.SECONDS)).isTrue();
}
/** Check that children are in the correct positions for being stacked. */
- private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
+ private void testStackedAtPosition(float x, float y) {
// Make sure the rest of the stack moved again, including the first bubble not moving, and
// is stacked to the right now that we're on the right side of the screen.
for (int i = 0; i < mLayout.getChildCount(); i++) {
- assertEquals(x + i * offsetMultiplier * mStackOffset,
- mLayout.getChildAt(i).getTranslationX(), 2f);
- assertEquals(y, mLayout.getChildAt(i).getTranslationY(), 2f);
+ assertEquals(x, mLayout.getChildAt(i).getTranslationX(), 2f);
+ assertEquals(y + Math.min(i, 1) * mStackOffset, mLayout.getChildAt(i).getTranslationY(),
+ 2f);
assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f);
}
}
@@ -175,4 +184,22 @@
mLayout.getChildAt(i).getTranslationY(), 2f);
}
}
+
+ private void waitForAnimation() throws Exception {
+ final Semaphore semaphore = new Semaphore(0);
+ boolean[] animating = new boolean[]{ true };
+ for (int i = 0; i < 4; i++) {
+ if (animating[0]) {
+ mMainThreadHandler.post(() -> {
+ if (!mExpandedController.isAnimating()) {
+ animating[0] = false;
+ semaphore.release();
+ }
+ });
+ Thread.sleep(500);
+ }
+ }
+ assertThat(semaphore.tryAcquire(1, 2, TimeUnit.SECONDS)).isTrue();
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
index 48ae296..2ed5add 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -164,11 +164,17 @@
@Override
public void cancelAllAnimations() {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(super::cancelAllAnimations);
}
@Override
public void cancelAnimationsOnView(View view) {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(() -> super.cancelAnimationsOnView(view));
}
@@ -221,6 +227,9 @@
@Override
protected void startPathAnimation() {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(super::startPathAnimation);
}
}
@@ -322,4 +331,9 @@
e.printStackTrace();
}
}
+
+ /** Waits for the main thread to finish processing all pending runnables. */
+ public void waitForMainThread() {
+ runOnMainThreadAndBlock(() -> {});
+ }
}