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

Commit 2c90f3b1 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Handle non-app windows in app rotation transition asynchronously

When starting activity in different orientation with shell transition,
the display will be rotated immediately. To avoid jump cut of non-app
windows by the change, those windows will be faded out in old rotation.
And when the app transition is done, the windows will be faded in with
current rotation.

This also reduces transition latency because the transition can start
once the activities are ready, without waiting for those non-activity
window to complete redraw.

Bug: 210839369
Test: atest TransitionTests#testAppTransitionWithRotationChange
Test: adb shell setprop persist.debug.shell_transit 1; reboot
      Launch landscape app from portrait home.

Change-Id: I0afc3fcdb3b2ee9d23d5d4addc488ae085a2afd8
parent 9975e2c4
Loading
Loading
Loading
Loading
+15 −6
Original line number Diff line number Diff line
@@ -1843,6 +1843,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
        }
    }

    /** Returns {@code true} if the decided new rotation has not applied to configuration yet. */
    private boolean isRotationChanging() {
        return mDisplayRotation.getRotation() != getWindowConfiguration().getRotation();
    }

    private void startFadeRotationAnimationIfNeeded() {
        if (isRotationChanging()) {
            startFadeRotationAnimation(false /* shouldDebounce */);
        }
    }

    /**
     * Starts the hide animation for the windows which will be rotated seamlessly.
     *
@@ -3202,11 +3213,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp

        // Hide the windows which are not significant in rotation animation. So that the windows
        // don't need to block the unfreeze time.
        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()
                // Do not fade for freezing without rotation change.
                && mDisplayRotation.getRotation() != getWindowConfiguration().getRotation()
                && mFadeRotationAnimationController == null) {
            startFadeRotationAnimation(false /* shouldDebounce */);
        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
            startFadeRotationAnimationIfNeeded();
        }
    }

@@ -3227,6 +3235,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
            }
            if (!controller.isCollecting(this)) {
                controller.collect(this);
                startFadeRotationAnimationIfNeeded();
            }
            return;
        }
@@ -3234,7 +3243,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
                this, this, null /* remoteTransition */, displayChange);
        if (t != null) {
            mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
            if (getRotation() != getWindowConfiguration().getRotation()) {
            if (isRotationChanging()) {
                mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
                controller.mTransitionMetricsReporter.associate(t,
                        startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
+6 −19
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;

import android.annotation.NonNull;
import android.content.Context;
import android.util.ArrayMap;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
import android.view.animation.Animation;
@@ -38,7 +37,6 @@ import java.io.PrintWriter;
public class FadeAnimationController {
    protected final DisplayContent mDisplayContent;
    protected final Context mContext;
    protected final ArrayMap<WindowToken, Runnable> mDeferredFinishCallbacks = new ArrayMap<>();

    public FadeAnimationController(DisplayContent displayContent) {
        mDisplayContent = displayContent;
@@ -78,16 +76,8 @@ public class FadeAnimationController {
            return;
        }

        // We deferred the end of the animation when hiding the token, so we need to end it now that
        // it's shown again.
        final SurfaceAnimator.OnAnimationFinishedCallback finishedCallback = show ? (t, r) -> {
            final Runnable runnable = mDeferredFinishCallbacks.remove(windowToken);
            if (runnable != null) {
                runnable.run();
            }
        } : null;
        windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter,
                show /* hidden */, animationType, finishedCallback);
                show /* hidden */, animationType, null /* finishedCallback */);
    }

    protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
@@ -135,7 +125,7 @@ public class FadeAnimationController {
        };
    }

    protected class FadeAnimationAdapter extends LocalAnimationAdapter {
    protected static class FadeAnimationAdapter extends LocalAnimationAdapter {
        protected final boolean mShow;
        protected final WindowToken mToken;

@@ -149,13 +139,10 @@ public class FadeAnimationController {

        @Override
        public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
            // We defer the end of the hide animation to ensure the tokens stay hidden until
            // we show them again.
            if (!mShow) {
                mDeferredFinishCallbacks.put(mToken, endDeferFinishCallback);
                return true;
            }
            return false;
            // Defer the finish callback (restore leash) of the hide animation to ensure the token
            // stay hidden until it needs to show again. Besides, when starting the show animation,
            // the previous hide animation will be cancelled, so the callback can be ignored.
            return !mShow;
        }
    }
}
+86 −14
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ public class FadeRotationAnimationController extends FadeAnimationController {
    /** Whether to use constant zero alpha animation. */
    private boolean mHideImmediately;

    /** Whether this controller is triggered from shell transition. */
    /** Whether this controller is triggered from shell transition with type CHANGE. */
    private final boolean mIsChangeTransition;

    /** Whether the start transaction of the transition is committed (by shell). */
@@ -59,21 +59,30 @@ public class FadeRotationAnimationController extends FadeAnimationController {
    /** The list to store the drawn tokens before the rotation animation starts. */
    private ArrayList<WindowToken> mPendingShowTokens;

    /** It is used when the display has rotated, but some windows fade out in old rotation. */
    private SeamlessRotator mRotator;

    private final int mOriginalRotation;
    private final boolean mHasScreenRotationAnimation;

    public FadeRotationAnimationController(DisplayContent displayContent) {
        super(displayContent);
        mService = displayContent.mWmService;
        mIsChangeTransition = displayContent.inTransition()
                && displayContent.mTransitionController.getCollectingTransitionType()
                == WindowManager.TRANSIT_CHANGE;
        mOriginalRotation = displayContent.getWindowConfiguration().getRotation();
        final int transitionType =
                displayContent.mTransitionController.getCollectingTransitionType();
        mIsChangeTransition = transitionType == WindowManager.TRANSIT_CHANGE;
        // Only CHANGE type (rotation animation) needs to wait for the start transaction.
        mIsStartTransactionCommitted = !mIsChangeTransition;
        mTimeoutRunnable = displayContent.getRotationAnimation() != null
                || mIsChangeTransition ? () -> {
        mTimeoutRunnable = displayContent.inTransition() ? () -> {
            synchronized (mService.mGlobalLock) {
                displayContent.finishFadeRotationAnimationIfPossible();
                mService.mWindowPlacerLocked.performSurfacePlacement();
            }
        } : null;
        if (mTimeoutRunnable != null) {
        mHasScreenRotationAnimation =
                displayContent.getRotationAnimation() != null || mIsChangeTransition;
        if (mHasScreenRotationAnimation) {
            // Hide the windows immediately because screen should have been covered by screenshot.
            mHideImmediately = true;
        }
@@ -103,6 +112,19 @@ public class FadeRotationAnimationController extends FadeAnimationController {
        }, true /* traverseTopToBottom */);
    }

    @Override
    public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
        if (show) {
            final SurfaceControl leash = mTargetWindowTokens.remove(windowToken);
            if (leash != null && mRotator != null) {
                // The leash was unrotated by start transaction of transition. Clear the transform
                // to reshow the window in current rotation.
                mRotator.setIdentityMatrix(mDisplayContent.getPendingTransaction(), leash);
            }
        }
        super.fadeWindowToken(show, windowToken, animationType);
    }

    /** Applies show animation on the previously hidden window tokens. */
    void show() {
        for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
@@ -125,19 +147,23 @@ public class FadeRotationAnimationController extends FadeAnimationController {
     * controller is created for normal rotation.
     */
    boolean show(WindowToken token) {
        if (!isTargetToken(token)) return false;
        if (!mIsStartTransactionCommitted) {
            // The fade-in animation should only start after the screenshot layer is shown by shell.
            // Otherwise the window will be blinking before the rotation animation starts. So store
            // to a pending list and animate them until the transaction is committed.
            if (mTargetWindowTokens.containsKey(token)) {
            if (mPendingShowTokens == null) {
                mPendingShowTokens = new ArrayList<>();
            }
            mPendingShowTokens.add(token);
            return false;
        }
        if (!mHasScreenRotationAnimation && token.mTransitionController.inTransition()) {
            // Defer showing to onTransitionFinished().
            return false;
        }
        if (mTimeoutRunnable != null && mTargetWindowTokens.remove(token) != null) {
        // If the timeout runnable is null (fixed rotation), the case will be handled by show().
        if (mTimeoutRunnable != null) {
            fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
            if (mTargetWindowTokens.isEmpty()) {
                mService.mH.removeCallbacks(mTimeoutRunnable);
@@ -177,6 +203,15 @@ public class FadeRotationAnimationController extends FadeAnimationController {
        return mTargetWindowTokens.containsKey(token);
    }

    /**
     * Whether the insets animation leash should use previous position when running fade out
     * animation in rotated display.
     */
    boolean shouldFreezeInsetsPosition(WindowState w) {
        return !mHasScreenRotationAnimation && w.mTransitionController.inTransition()
                && isTargetToken(w.mToken);
    }

    void setOnShowRunnable(Runnable onShowRunnable) {
        mOnShowRunnable = onShowRunnable;
    }
@@ -186,6 +221,22 @@ public class FadeRotationAnimationController extends FadeAnimationController {
     * transition starts. And associate transaction callback to consume pending animations.
     */
    void setupStartTransaction(SurfaceControl.Transaction t) {
        if (!mIsChangeTransition) {
            // Take OPEN/CLOSE transition type as the example, the non-activity windows need to
            // fade out in previous rotation while display has rotated to the new rotation, so
            // their leashes are unrotated with the start transaction.
            mRotator = new SeamlessRotator(mOriginalRotation,
                    mDisplayContent.getWindowConfiguration().getRotation(),
                    mDisplayContent.getDisplayInfo(),
                    false /* applyFixedTransformationHint */);
            for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
                final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
                if (leash != null) {
                    mRotator.applyTransform(t, leash);
                }
            }
            return;
        }
        // Hide the windows immediately because a screenshot layer should cover the screen.
        for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
            final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
@@ -208,9 +259,30 @@ public class FadeRotationAnimationController extends FadeAnimationController {
        });
    }

    void onTransitionFinished() {
        if (mIsChangeTransition) {
            // With screen rotation animation, the windows are always faded in when they are drawn.
            // Because if they are drawn fast enough, the fade animation should not be observable.
            return;
        }
        // For other transition types, the fade-in animation runs after the transition to make the
        // transition animation (e.g. launch activity) look cleaner.
        for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
            final WindowToken token = mTargetWindowTokens.keyAt(i);
            for (int j = token.getChildCount() - 1; j >= 0; j--) {
                // Only fade in the drawn windows. If the remaining windows are drawn later,
                // show(WindowToken) will be called to fade in them.
                if (token.getChildAt(j).isDrawFinishedLw()) {
                    mDisplayContent.finishFadeRotationAnimation(token);
                    break;
                }
            }
        }
    }

    @Override
    public Animation getFadeInAnimation() {
        if (mTimeoutRunnable != null) {
        if (mHasScreenRotationAnimation) {
            // Use a shorter animation so it is easier to align with screen rotation animation.
            return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
        }
+8 −0
Original line number Diff line number Diff line
@@ -297,6 +297,14 @@ class InsetsSourceProvider {
    }

    private Point getWindowFrameSurfacePosition() {
        if (mControl != null) {
            final FadeRotationAnimationController fadeController =
                    mWin.mDisplayContent.getFadeRotationAnimationController();
            if (fadeController != null && fadeController.shouldFreezeInsetsPosition(mWin)) {
                // Use previous position because the fade-out animation runs in old rotation.
                return mControl.getSurfacePosition();
            }
        }
        final Rect frame = mWin.getFrame();
        final Point position = new Point();
        mWin.transformFrameToSurfacePosition(frame.left, frame.top, position);
+0 −8
Original line number Diff line number Diff line
@@ -95,14 +95,6 @@ public class NavBarFadeAnimationController extends FadeAnimationController{
            } else {
                fadeAnim.run();
            }
        } else {
            // If fade rotation animation is running and controlling the nav bar, make sure we empty
            // the mDeferredFinishCallbacks and defer the runnable until fade rotation animation
            // finishes.
            final Runnable runnable = mDeferredFinishCallbacks.remove(mNavigationBar.mToken);
            if (runnable != null) {
                controller.setOnShowRunnable(runnable);
            }
        }
    }

Loading