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

Commit 201e538f authored by Nick Chameyev's avatar Nick Chameyev
Browse files

Finish unfold transition after timeout

Adds a safeguard to finish the unfold Shell transition animation in
case if UnfoldTransitionProgressProvider won't send the 'animation
finish' event or it has already sent it by the time we received
startAnimation.

This should happen rarely, either due to a timing difference
(startAnimation is called way too late, more than ~1 second
after turning on the screen) or if there is a bug in the implementation
causing the animation not to finish. These problems should be addressed
as part of b/318803244.

Bug: 372319646
Test: atest UnfoldTransitionHandlerTest
Test: remove transtion finishing, fold/unfold
  => verify that transition is finished after timeout
Test: fold multiple times quickly using a script
  => verify all transitions are finished
Flag: EXEMPT bugfix
Change-Id: I75176986ac815c45216731ffa42225d2748f3456
parent 5282b2b0
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -596,9 +596,10 @@ public abstract class WMShellModule {
            TransactionPool transactionPool,
            Transitions transitions,
            @ShellMainThread ShellExecutor executor,
            @ShellMainThread Handler handler,
            ShellInit shellInit) {
        return new UnfoldTransitionHandler(shellInit, progressProvider.get(), animator,
                unfoldAnimator, transactionPool, executor, transitions);
                unfoldAnimator, transactionPool, executor, handler, transitions);
    }

    @WMSingleton
+22 −0
Original line number Diff line number Diff line
@@ -24,7 +24,9 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITI

import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.os.Handler;
import android.os.IBinder;
import android.util.Slog;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -33,6 +35,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.shared.TransactionPool;
@@ -59,6 +62,10 @@ import java.util.concurrent.Executor;
 */
public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListener {

    private static final String TAG = "UnfoldTransitionHandler";
    @VisibleForTesting
    static final int FINISH_ANIMATION_TIMEOUT_MILLIS = 5_000;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            DefaultDisplayChange.DEFAULT_DISPLAY_NO_CHANGE,
@@ -75,6 +82,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
    private final Transitions mTransitions;
    private final Executor mExecutor;
    private final TransactionPool mTransactionPool;
    private final Handler mHandler;

    @Nullable
    private TransitionFinishCallback mFinishCallback;
@@ -87,17 +95,25 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
    private float mLastAnimationProgress = 0.0f;
    private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>();

    private final Runnable mAnimationPlayingTimeoutRunnable = () -> {
        Slog.wtf(TAG, "Timeout occurred when playing the unfold animation, "
                + "force finishing the transition");
        finishTransitionIfNeeded();
    };

    public UnfoldTransitionHandler(ShellInit shellInit,
            ShellUnfoldProgressProvider unfoldProgressProvider,
            FullscreenUnfoldTaskAnimator fullscreenUnfoldAnimator,
            SplitTaskUnfoldAnimator splitUnfoldTaskAnimator,
            TransactionPool transactionPool,
            Executor executor,
            Handler handler,
            Transitions transitions) {
        mUnfoldProgressProvider = unfoldProgressProvider;
        mTransitions = transitions;
        mTransactionPool = transactionPool;
        mExecutor = executor;
        mHandler = handler;

        mAnimators.add(splitUnfoldTaskAnimator);
        mAnimators.add(fullscreenUnfoldAnimator);
@@ -159,6 +175,11 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
        // finish shell transition immediately
        if (mAnimationFinished) {
            finishTransitionIfNeeded();
        } else {
            // TODO: b/318803244 - remove timeout handling when we could guarantee that
            //  the animation will be always finished after receiving startAnimation
            mHandler.removeCallbacks(mAnimationPlayingTimeoutRunnable);
            mHandler.postDelayed(mAnimationPlayingTimeoutRunnable, FINISH_ANIMATION_TIMEOUT_MILLIS);
        }

        return true;
@@ -333,6 +354,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
            animator.stop();
        }

        mHandler.removeCallbacks(mAnimationPlayingTimeoutRunnable);
        mFinishCallback.onTransitionFinished(null);
        mFinishCallback = null;
        mTransition = null;
+30 −0
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_NONE;

import static com.android.wm.shell.unfold.UnfoldTransitionHandler.FINISH_ANIMATION_TIMEOUT_MILLIS;

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

import static org.mockito.ArgumentMatchers.any;
@@ -32,7 +34,9 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.graphics.Rect;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.test.TestLooper;
import android.view.Display;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -67,6 +71,8 @@ public class UnfoldTransitionHandlerTest {
    private FullscreenUnfoldTaskAnimator mFullscreenUnfoldTaskAnimator;
    private SplitTaskUnfoldAnimator mSplitTaskUnfoldAnimator;
    private Transitions mTransitions;
    private TestLooper mTestLooper;
    private Handler mHandler;

    private final IBinder mTransition = new Binder();

@@ -75,6 +81,9 @@ public class UnfoldTransitionHandlerTest {
        final ShellExecutor executor = new TestSyncExecutor();
        final ShellInit shellInit = new ShellInit(executor);

        mTestLooper = new TestLooper();
        mHandler = new Handler(mTestLooper.getLooper());

        mFullscreenUnfoldTaskAnimator = mock(FullscreenUnfoldTaskAnimator.class);
        mSplitTaskUnfoldAnimator = mock(SplitTaskUnfoldAnimator.class);
        mTransitions = mock(Transitions.class);
@@ -86,6 +95,7 @@ public class UnfoldTransitionHandlerTest {
                mSplitTaskUnfoldAnimator,
                mTransactionPool,
                executor,
                mHandler,
                mTransitions
        );

@@ -159,6 +169,7 @@ public class UnfoldTransitionHandlerTest {
        TransitionFinishCallback mergeFinishCallback = mock(TransitionFinishCallback.class);
        mUnfoldTransitionHandler.mergeAnimation(new Binder(), createFoldTransitionInfo(),
                mock(SurfaceControl.Transaction.class), mTransition, mergeFinishCallback);
        mTestLooper.dispatchAll();

        // Verify that fold transition is merged into unfold and that unfold is finished
        final InOrder inOrder = inOrder(mergeFinishCallback, finishCallback);
@@ -200,6 +211,25 @@ public class UnfoldTransitionHandlerTest {
        assertThat(animationStarted).isTrue();
    }

    @Test
    public void startAnimation_animationHasNotFinishedAfterTimeout_finishesTheTransition() {
        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
        mUnfoldTransitionHandler.startAnimation(
                mTransition,
                mock(TransitionInfo.class),
                mock(SurfaceControl.Transaction.class),
                mock(SurfaceControl.Transaction.class),
                finishCallback
        );

        mTestLooper.moveTimeForward(FINISH_ANIMATION_TIMEOUT_MILLIS + 1);
        mTestLooper.dispatchAll();

        verify(finishCallback).onTransitionFinished(any());
    }

    @Test
    public void startAnimation_differentTransitionFromRequestWithResize_doesNotStartAnimation() {
        mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo());