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

Commit 4fe9c2c3 authored by Nick Chameyev's avatar Nick Chameyev
Browse files

Take over animating of unfold Shell transitions

Adds logic to UnfoldTransitionHandler that
takes over animating a transition if it contains
changes related to unfold.

Bug: 259220649
Test: atest PhysicalDisplaySwitchTransitionLauncherMixedTest
Test: manual unfold 20 times =>
  verify that there is no black screen delay
Test: artificially increase collecting of rotation transition
  => rotate and unfold device, check that animation works
Flag: handle_mixed_unfold_transitions
Change-Id: I3285c1b6a45da63ac7846e9d5929db9426b069ae
parent 96ef4624
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -961,6 +961,12 @@
      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
    },
    "-1204565480": {
      "message": "Adding display switch to existing collecting transition",
      "level": "DEBUG",
      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
      "at": "com\/android\/server\/wm\/PhysicalDisplaySwitchTransitionLauncher.java"
    },
    "-1198579104": {
      "message": "Pushing next activity %s out to target's task %s",
      "level": "VERBOSE",
+39 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;

import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;

import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -74,9 +75,9 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
            Executor executor,
            Transitions transitions) {
        mUnfoldProgressProvider = unfoldProgressProvider;
        mTransitions = transitions;
        mTransactionPool = transactionPool;
        mExecutor = executor;
        mTransitions = transitions;

        mAnimators.add(splitUnfoldTaskAnimator);
        mAnimators.add(fullscreenUnfoldAnimator);
@@ -104,6 +105,16 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull TransitionFinishCallback finishCallback) {
        if (hasUnfold(info) && transition != mTransition) {
            // Take over transition that has unfold, we might receive it if no other handler
            // accepted request in handleRequest, e.g. for rotation + unfold or
            // TRANSIT_NONE + unfold transitions
            mTransition = transition;

            ProtoLog.v(WM_SHELL_TRANSITIONS, "UnfoldTransitionHandler: "
                    + "take over startAnimation");
        }

        if (transition != mTransition) return false;

        for (int i = 0; i < mAnimators.size(); i++) {
@@ -203,6 +214,33 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
                && request.getDisplayChange().isPhysicalDisplayChanged());
    }

    /** Whether `transitionInfo` contains an unfold action. */
    public boolean hasUnfold(@NonNull TransitionInfo transitionInfo) {
        // Unfold animation won't play when animations are disabled
        if (!ValueAnimator.areAnimatorsEnabled()) return false;

        for (int i = 0; i < transitionInfo.getChanges().size(); i++) {
            final TransitionInfo.Change change = transitionInfo.getChanges().get(i);
            if ((change.getFlags() & TransitionInfo.FLAG_IS_DISPLAY) != 0) {
                if (change.getEndAbsBounds() == null || change.getStartAbsBounds() == null) {
                    continue;
                }

                // Handle only unfolding, currently we don't have an animation when folding
                final int afterArea =
                        change.getEndAbsBounds().width() * change.getEndAbsBounds().height();
                final int beforeArea = change.getStartAbsBounds().width()
                        * change.getStartAbsBounds().height();

                if (afterArea > beforeArea) {
                    return true;
                }
            }
        }

        return false;
    }

    @Nullable
    @Override
    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+95 −0
Original line number Diff line number Diff line
@@ -17,9 +17,12 @@
package com.android.wm.shell.unfold;

import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_NONE;

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

import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -27,6 +30,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.app.ActivityManager;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.view.Display;
@@ -35,6 +39,9 @@ import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;

import com.android.window.flags.FakeFeatureFlagsImpl;
import com.android.window.flags.FeatureFlags;
import com.android.window.flags.Flags;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.sysui.ShellInit;
@@ -45,6 +52,8 @@ import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.util.ArrayList;
import java.util.List;
@@ -131,6 +140,55 @@ public class UnfoldTransitionHandlerTest {
        verify(finishCallback, never()).onTransitionFinished(any());
    }

    @Test
    public void startAnimation_sameTransitionAsHandleRequest_startsAnimation() {
        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);

        boolean animationStarted = mUnfoldTransitionHandler.startAnimation(
                mTransition,
                mock(TransitionInfo.class),
                mock(SurfaceControl.Transaction.class),
                mock(SurfaceControl.Transaction.class),
                finishCallback
        );

        assertThat(animationStarted).isTrue();
    }

    @Test
    public void startAnimation_differentTransitionFromRequestWithUnfold_startsAnimation() {
        mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo());
        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);

        boolean animationStarted = mUnfoldTransitionHandler.startAnimation(
                mTransition,
                createUnfoldTransitionInfo(),
                mock(SurfaceControl.Transaction.class),
                mock(SurfaceControl.Transaction.class),
                finishCallback
        );

        assertThat(animationStarted).isTrue();
    }

    @Test
    public void startAnimation_differentTransitionFromRequestWithoutUnfold_doesNotStart() {
        mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo());
        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);

        boolean animationStarted = mUnfoldTransitionHandler.startAnimation(
                mTransition,
                createNonUnfoldTransitionInfo(),
                mock(SurfaceControl.Transaction.class),
                mock(SurfaceControl.Transaction.class),
                finishCallback
        );

        assertThat(animationStarted).isFalse();
    }

    @Test
    public void startAnimation_animationFinishes_finishesTheTransition() {
        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
@@ -215,6 +273,12 @@ public class UnfoldTransitionHandlerTest {
                triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */);
    }

    private TransitionRequestInfo createNoneTransitionInfo() {
        return new TransitionRequestInfo(TRANSIT_NONE,
                /* triggerTask= */ null, /* remoteTransition= */ null,
                /* displayChange= */ null,  /* flags= */ 0);
    }

    private static class TestShellUnfoldProgressProvider implements ShellUnfoldProgressProvider,
            ShellUnfoldProgressProvider.UnfoldListener {

@@ -277,4 +341,35 @@ public class UnfoldTransitionHandlerTest {
            return false;
        }
    }

    static class TestCase {
        private final boolean mShouldHandleMixedUnfold;

        public TestCase(boolean shouldHandleMixedUnfold) {
            mShouldHandleMixedUnfold = shouldHandleMixedUnfold;
        }

        public boolean mixedUnfoldFlagEnabled() {
            return mShouldHandleMixedUnfold;
        }

        @Override
        public String toString() {
            return "shouldHandleMixedUnfold flag = " + mShouldHandleMixedUnfold;
        }
    }

    private TransitionInfo createUnfoldTransitionInfo() {
        TransitionInfo transitionInfo = new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0);
        TransitionInfo.Change change = new TransitionInfo.Change(null, mock(SurfaceControl.class));
        change.setStartAbsBounds(new Rect(0, 0, 10, 10));
        change.setEndAbsBounds(new Rect(0, 0, 100, 100));
        change.setFlags(TransitionInfo.FLAG_IS_DISPLAY);
        transitionInfo.addChange(change);
        return transitionInfo;
    }

    private TransitionInfo createNonUnfoldTransitionInfo() {
        return new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0);
    }
}
 No newline at end of file
+22 −7
Original line number Diff line number Diff line
@@ -34,6 +34,8 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.wm.DeviceStateController.DeviceState;

public class PhysicalDisplaySwitchTransitionLauncher {
@@ -117,14 +119,27 @@ public class PhysicalDisplaySwitchTransitionLauncher {
        displayChange.setEndAbsBounds(endAbsBounds);
        displayChange.setPhysicalDisplayChanged(true);

        final Transition t = mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE,
        mTransition = null;

        if (mTransitionController.isCollecting()) {
            // Add display container to the currently collecting transition
            mTransitionController.collect(mDisplayContent);
            mTransition = mTransitionController.getCollectingTransition();

            // Make sure that transition is not ready until we finish the remote display change
            mTransition.setReady(mDisplayContent, false);

            ProtoLog.d(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "Adding display switch to existing collecting transition");
        } else {
            mTransition = mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE,
                    0 /* flags */,
                    mDisplayContent, mDisplayContent, null /* remoteTransition */,
                    displayChange);
        }

        if (t != null) {
            mDisplayContent.mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
            mTransition = t;
        if (mTransition != null) {
            mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
        }

        mShouldRequestTransitionOnDisplaySwitch = false;
+27 −2
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -61,8 +62,7 @@ import org.mockito.MockitoAnnotations;
 */
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class PhysicalDisplaySwitchTransitionLauncherTest extends WindowTestsBase {
public class PhysicalDisplaySwitchTransitionLauncherTest {

    @Mock
    DisplayContent mDisplayContent;
@@ -73,6 +73,8 @@ public class PhysicalDisplaySwitchTransitionLauncherTest extends WindowTestsBase
    @Mock
    ActivityTaskManagerService mActivityTaskManagerService;
    @Mock
    BLASTSyncEngine mSyncEngine;
    @Mock
    TransitionController mTransitionController;

    private PhysicalDisplaySwitchTransitionLauncher mTarget;
@@ -216,6 +218,20 @@ public class PhysicalDisplaySwitchTransitionLauncherTest extends WindowTestsBase
        assertTransitionNotRequested();
    }

    @Test
    public void testDisplaySwitchAfterUnfolding_otherCollectingTransition_collectsDisplaySwitch() {
        givenCollectingTransition(createTransition(TRANSIT_CHANGE));
        givenAllAnimationsEnabled();
        mTarget.foldStateChanged(FOLDED);

        mTarget.foldStateChanged(OPEN);
        requestDisplaySwitch();

        // Collects to the current transition
        verify(mTransitionController).collect(mDisplayContent);
    }


    @Test
    public void testDisplaySwitch_whenNoContentInDisplayContent_noTransition() {
        givenAllAnimationsEnabled();
@@ -267,6 +283,15 @@ public class PhysicalDisplaySwitchTransitionLauncherTest extends WindowTestsBase
        when(mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled);
    }

    private void givenCollectingTransition(@Nullable Transition transition) {
        when(mTransitionController.isCollecting()).thenReturn(transition != null);
        when(mTransitionController.getCollectingTransition()).thenReturn(transition);
    }

    private Transition createTransition(int type) {
        return new Transition(type, /* flags= */ 0, mTransitionController, mSyncEngine);
    }

    private void givenDisplayContentHasContent(boolean hasContent) {
        when(mDisplayContent.getLastHasContent()).thenReturn(hasContent);
    }