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

Commit 4b4fc236 authored by Vadim Caen's avatar Vadim Caen Committed by Riddle Hsu
Browse files

System Bars animation for fixed rotation transform

Fade out system bars when the fixed transform is starting and fade
them in once its finished.

In other words, if mFixedRotationLaunchingApp is set, the hide
(fade out) animation will start. And if mFixedRotationLaunchingApp
is cleared or seamless rotation is done for fixed rotation, the
show (fade in) animation will start.

Note a special case: the fixed rotation app hides the system bars,
so there may be no window to rotate. Then the show animation will
apply immediately (though they are invisible) to make sure the bars
can be visible by transient state.

Bug: 150676212
Bug: 156864004
Test: Manual: open and close a landscape application, and open and
      immediately cancel opening animation.
      Manual: open a landscape application which hides status bar
      and navigation bar. The bars should still be visible after
      swiping from edge of screen.
Test: WmTests:DisplayContentTests#testApplyTopFixedRotationTransform

Change-Id: Ic326b4bda44b39c875aeb27f33fba31a5cfcfc66
parent 92d678d8
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -498,6 +498,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
     */
    private ActivityRecord mFixedRotationLaunchingApp;

    private FixedRotationAnimationController mFixedRotationAnimationController;

    final FixedRotationTransitionListener mFixedRotationTransitionListener =
            new FixedRotationTransitionListener();

@@ -1479,6 +1481,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
        return mFixedRotationLaunchingApp;
    }

    @VisibleForTesting
    @Nullable FixedRotationAnimationController getFixedRotationAnimationController() {
        return mFixedRotationAnimationController;
    }

    void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
        setFixedRotationLaunchingAppUnchecked(r, ROTATION_UNDEFINED);
    }
@@ -1486,8 +1493,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
    void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
        if (mFixedRotationLaunchingApp == null && r != null) {
            mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
            if (mFixedRotationAnimationController == null) {
                mFixedRotationAnimationController = new FixedRotationAnimationController(this);
                mFixedRotationAnimationController.hide();
            }
        } else if (mFixedRotationLaunchingApp != null && r == null) {
            mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
            finishFixedRotationAnimationIfPossible();
        }
        mFixedRotationLaunchingApp = r;
    }
@@ -1576,6 +1588,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
        }
    }

    /** Re-show the previously hidden windows if all seamless rotated windows are done. */
    void finishFixedRotationAnimationIfPossible() {
        final FixedRotationAnimationController controller = mFixedRotationAnimationController;
        if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
            controller.show();
            mFixedRotationAnimationController = null;
        }
    }

    /**
     * Update rotation of the display.
     *
+6 −0
Original line number Diff line number Diff line
@@ -560,6 +560,7 @@ public class DisplayRotation {
        }, true /* traverseTopToBottom */);
        mSeamlessRotationCount = 0;
        mRotatingSeamlessly = false;
        mDisplayContent.finishFixedRotationAnimationIfPossible();
    }

    private void prepareSeamlessRotation() {
@@ -573,6 +574,10 @@ public class DisplayRotation {
        return mRotatingSeamlessly;
    }

    boolean hasSeamlessRotatingWindow() {
        return mSeamlessRotationCount > 0;
    }

    @VisibleForTesting
    boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
        // Display doesn't need to be frozen because application has been started in correct
@@ -646,6 +651,7 @@ public class DisplayRotation {
                    "Performing post-rotate rotation after seamless rotation");
            // Finish seamless rotation.
            mRotatingSeamlessly = false;
            mDisplayContent.finishFixedRotationAnimationIfPossible();

            updateRotationAndSendNewConfigIfChanged();
        }
+166 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.wm;

import static com.android.server.wm.AnimationSpecProto.WINDOW;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;

import android.content.Context;
import android.util.ArrayMap;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Transformation;

import com.android.internal.R;

import java.io.PrintWriter;
import java.util.ArrayList;

/**
 * Controller to fade out and in system ui when applying a fixed rotation transform to a window
 * token.
 *
 * The system bars will be fade out when the fixed rotation transform starts and will be fade in
 * once all surfaces have been rotated.
 */
public class FixedRotationAnimationController {

    private final Context mContext;
    private final WindowState mStatusBar;
    private final WindowState mNavigationBar;
    private final ArrayList<WindowToken> mAnimatedWindowToken = new ArrayList<>(2);
    private final ArrayMap<WindowToken, Runnable> mDeferredFinishCallbacks = new ArrayMap<>();

    public FixedRotationAnimationController(DisplayContent displayContent) {
        mContext = displayContent.mWmService.mContext;
        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
        mStatusBar = displayPolicy.getStatusBar();
        // Do not animate movable navigation bar (e.g. non-gesture mode).
        mNavigationBar = !displayPolicy.navigationBarCanMove()
                ? displayPolicy.getNavigationBar()
                : null;
    }

    /** Applies show animation on the previously hidden window tokens. */
    void show() {
        for (int i = mAnimatedWindowToken.size() - 1; i >= 0; i--) {
            final WindowToken windowToken = mAnimatedWindowToken.get(i);
            fadeWindowToken(true /* show */, windowToken);
        }
    }

    /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
    void hide() {
        if (mNavigationBar != null) {
            fadeWindowToken(false /* show */, mNavigationBar.mToken);
        }
        if (mStatusBar != null) {
            fadeWindowToken(false /* show */, mStatusBar.mToken);
        }
    }

    private void fadeWindowToken(boolean show, WindowToken windowToken) {
        if (windowToken == null || windowToken.getParent() == null) {
            return;
        }

        final Animation animation = AnimationUtils.loadAnimation(mContext,
                show ? R.anim.fade_in : R.anim.fade_out);
        final LocalAnimationAdapter.AnimationSpec windowAnimationSpec =
                createAnimationSpec(animation);

        final FixedRotationAnimationAdapter animationAdapter = new FixedRotationAnimationAdapter(
                windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, windowToken);

        // 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 */, ANIMATION_TYPE_FIXED_TRANSFORM, finishedCallback);
        mAnimatedWindowToken.add(windowToken);
    }

    private LocalAnimationAdapter.AnimationSpec createAnimationSpec(Animation animation) {
        return new LocalAnimationAdapter.AnimationSpec() {

            final Transformation mTransformation = new Transformation();

            @Override
            public boolean getShowWallpaper() {
                return true;
            }

            @Override
            public long getDuration() {
                return animation.getDuration();
            }

            @Override
            public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
                    long currentPlayTime) {
                mTransformation.clear();
                animation.getTransformation(currentPlayTime, mTransformation);
                t.setAlpha(leash, mTransformation.getAlpha());
            }

            @Override
            public void dump(PrintWriter pw, String prefix) {
                pw.print(prefix);
                pw.println(animation);
            }

            @Override
            public void dumpDebugInner(ProtoOutputStream proto) {
                final long token = proto.start(WINDOW);
                proto.write(ANIMATION, animation.toString());
                proto.end(token);
            }
        };
    }

    private class FixedRotationAnimationAdapter extends LocalAnimationAdapter {
        private final boolean mShow;
        private final WindowToken mToken;

        FixedRotationAnimationAdapter(AnimationSpec windowAnimationSpec,
                SurfaceAnimationRunner surfaceAnimationRunner, boolean show,
                WindowToken token) {
            super(windowAnimationSpec, surfaceAnimationRunner);
            mShow = show;
            mToken = token;
        }

        @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;
        }
    }
}
+8 −1
Original line number Diff line number Diff line
@@ -488,6 +488,12 @@ class SurfaceAnimator {
     */
    static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5;

    /**
     * Animation when a fixed rotation transform is applied to a window token.
     * @hide
     */
    static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6;

    /**
     * Bitmask to include all animation types. This is NOT an {@link AnimationType}
     * @hide
@@ -505,7 +511,8 @@ class SurfaceAnimator {
            ANIMATION_TYPE_DIMMER,
            ANIMATION_TYPE_RECENTS,
            ANIMATION_TYPE_WINDOW_ANIMATION,
            ANIMATION_TYPE_INSETS_CONTROL
            ANIMATION_TYPE_INSETS_CONTROL,
            ANIMATION_TYPE_FIXED_TRANSFORM
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface AnimationType {}
+14 −1
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.same;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;

@@ -1059,6 +1060,11 @@ public class DisplayContentTests extends WindowTestsBase {
    @Test
    public void testApplyTopFixedRotationTransform() {
        mWm.mIsFixedRotationTransformEnabled = true;
        final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
        // Only non-movable (gesture) navigation bar will be animated by fixed rotation animation.
        doReturn(false).when(displayPolicy).navigationBarCanMove();
        displayPolicy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
        displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
        final Configuration config90 = new Configuration();
        mDisplayContent.computeScreenConfiguration(config90, ROTATION_90);

@@ -1079,6 +1085,12 @@ public class DisplayContentTests extends WindowTestsBase {
                ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
                false /* forceUpdate */));

        assertNotNull(mDisplayContent.getFixedRotationAnimationController());
        assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
                ANIMATION_TYPE_FIXED_TRANSFORM));
        assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
                ANIMATION_TYPE_FIXED_TRANSFORM));

        final Rect outFrame = new Rect();
        final Rect outInsets = new Rect();
        final Rect outStableInsets = new Rect();
@@ -1131,6 +1143,7 @@ public class DisplayContentTests extends WindowTestsBase {
        assertFalse(app.hasFixedRotationTransform());
        assertFalse(app2.hasFixedRotationTransform());
        assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
        assertNull(mDisplayContent.getFixedRotationAnimationController());
    }

    @Test
@@ -1293,7 +1306,7 @@ public class DisplayContentTests extends WindowTestsBase {
    }

    private static int getRotatedOrientation(DisplayContent dc) {
        return dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE
        return dc.mBaseDisplayWidth > dc.mBaseDisplayHeight
                ? SCREEN_ORIENTATION_PORTRAIT
                : SCREEN_ORIENTATION_LANDSCAPE;
    }