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

Commit c10cc4cc authored by Mady Mellor's avatar Mady Mellor
Browse files

Fix an issue where state wasn't set when animation interrupted

If the timing is right, when a new bubble is added to the stack while
it's in the midst of collapsing (or last bubble being removed after
being expanded), the new bubble becomes the "selected" bubble which
updates the view in ExpandedAnimationController -- when the view there
is updated, any ongoing animation is canceled.

When the animation is canceled, the collapseBackToStack runnable
may not get run as it depends on a % of the animation completing
before running in an animation update listener.

Setting the active controller for the physics animation layout
is dependent on this runnable being run -- resulting in inconsistent
state between the stack being expanded & the controller on the layout.

The fix for this is to run the runnable on cancel if it wasn't
already run.

Flag: com.android.wm.shell.multitasking.fix_bubbles_cancel_animation
Test: manual - post 2 bubbles every second and try to expand / dismiss
               them -- bubbles shouldn't get stuck on screen
Test: atest ExpandedViewAnimationControllerTest
Bug: 420487074
Change-Id: Iaeddaa8f139ab4b568c3622bde0e1af3d04928c7
parent f05204cd
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -463,7 +463,8 @@ public class BubbleExpandedView extends LinearLayout {
     * Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need
     * to be called after view inflate.
     */
    void initialize(BubbleExpandedViewManager expandedViewManager,
    @VisibleForTesting
    public void initialize(BubbleExpandedViewManager expandedViewManager,
            BubbleStackView stackView,
            BubblePositioner positioner,
            boolean isOverflow,
+12 −1
Original line number Diff line number Diff line
@@ -38,7 +38,9 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.Flags;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.bubbles.BubbleExpandedView;
import com.android.wm.shell.bubbles.BubblePositioner;
@@ -351,7 +353,8 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
        return false;
    }

    private AnimatorSet createCollapseAnimation(BubbleExpandedView expandedView,
    @VisibleForTesting
    AnimatorSet createCollapseAnimation(BubbleExpandedView expandedView,
            Runnable startStackCollapse, Runnable after) {
        List<Animator> animatorList = new ArrayList<>();
        animatorList.add(createHeightAnimation(expandedView));
@@ -370,6 +373,14 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationCancel(Animator animation) {
                if (Flags.fixBubblesCancelAnimation() && !notified[0]) {
                    notified[0] = true;
                    startStackCollapse.run();
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                after.run();
+86 −1
Original line number Diff line number Diff line
@@ -15,28 +15,42 @@
 */
package com.android.wm.shell.bubbles.animation;

import static com.android.wm.shell.Flags.FLAG_FIX_BUBBLES_CANCEL_ANIMATION;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.animation.AnimatorSet;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.ViewConfiguration;
import android.view.WindowManager;

import androidx.test.filters.SmallTest;

import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.bubbles.BubbleExpandedView;
import com.android.wm.shell.bubbles.BubbleExpandedViewManager;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.bubbles.BubbleTaskView;
import com.android.wm.shell.bubbles.TestableBubblePositioner;
import com.android.wm.shell.taskview.TaskView;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -47,11 +61,16 @@ import org.mockito.MockitoAnnotations;
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class ExpandedViewAnimationControllerTest extends ShellTestCase {

    private ExpandedViewAnimationController mController;
    @Rule
    public final SetFlagsRule setFlagsRule = new SetFlagsRule();

    private ExpandedViewAnimationControllerImpl mController;

    @Mock
    private BubbleExpandedView mMockExpandedView;

    private BubbleExpandedView mExpandedView;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
@@ -60,6 +79,14 @@ public class ExpandedViewAnimationControllerTest extends ShellTestCase {
                getContext().getSystemService(WindowManager.class));
        mController = new ExpandedViewAnimationControllerImpl(getContext(), positioner);

        mExpandedView = (BubbleExpandedView) LayoutInflater.from(getContext()).inflate(
                R.layout.bubble_expanded_view, null, false /* attachToRoot */);

        BubbleTaskView bubbleTaskView = mock(BubbleTaskView.class);
        when(bubbleTaskView.getTaskView()).thenReturn(mock(TaskView.class));
        mExpandedView.initialize(mock(BubbleExpandedViewManager.class), mock(BubbleStackView.class),
                mock(BubblePositioner.class), false, bubbleTaskView);

        mController.setExpandedView(mMockExpandedView);
        when(mMockExpandedView.getContentHeight()).thenReturn(1000);
    }
@@ -146,6 +173,64 @@ public class ExpandedViewAnimationControllerTest extends ShellTestCase {
        assertThat(mController.shouldCollapse()).isTrue();
    }

    @EnableFlags(FLAG_FIX_BUBBLES_CANCEL_ANIMATION)
    @Test
    public void testCollapseAnimation_canceledNotNotified() {
        Runnable stackCollapseRunnable = mock(Runnable.class);
        Runnable afterRunnable = mock(Runnable.class);
        mController.setExpandedView(mExpandedView);

        AnimatorSet animation = mController.createCollapseAnimation(mExpandedView,
                stackCollapseRunnable,
                afterRunnable);
        animation.setDuration(200);
        // Set playtime before the stackCollapseRunnable would be run
        animation.setCurrentPlayTime(10);
        animation.cancel();

        // Even though we canceled before the stackCollapseRunnable was run, it should get
        // run on cancel
        verify(stackCollapseRunnable, times(1)).run();
        verify(afterRunnable, times(1)).run();
    }

    @Test
    public void testCollapseAnimation_canceledHasNotified() {
        Runnable stackCollapseRunnable = mock(Runnable.class);
        Runnable afterRunnable = mock(Runnable.class);

        mController.setExpandedView(mExpandedView);
        AnimatorSet animation = mController.createCollapseAnimation(mExpandedView,
                stackCollapseRunnable,
                afterRunnable);
        animation.setDuration(200);
        final float duration = animation.getDuration();
        // Set playtime AFTER the stackCollapseRunnable would be run
        animation.setCurrentPlayTime(195);
        animation.cancel();
        // Both runnables are run only once
        verify(stackCollapseRunnable, times(1)).run();
        verify(afterRunnable, times(1)).run();
    }

    @Test
    public void testCollapseAnimation() {
        Runnable stackCollapseRunnable = mock(Runnable.class);
        Runnable afterRunnable = mock(Runnable.class);

        mController.setExpandedView(mExpandedView);
        AnimatorSet animation = mController.createCollapseAnimation(mExpandedView,
                stackCollapseRunnable,
                afterRunnable);
        animation.setDuration(200);
        animation.start();
        animation.setCurrentPlayTime(200);
        animation.end();

        verify(stackCollapseRunnable, times(1)).run();
        verify(afterRunnable, times(1)).run();
    }

    @Test
    public void testReset() {
        mController.updateDrag(100);