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

Commit 1a7dc4c2 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Only wait for significant windows to unfreeze display

This may reduce frozen time by ~60% (~100ms on a mid-end device)
when rotating device with a simple activity that can handle
orientation change (no relaunch).

The unfreezing procedure still waits for the windows of Activity,
IME, wallpaper. Other windows such as navigation bar, status bar,
toast, overlay, they are hidden when rotation animation is set for
freezing screen. And if they are redrawn in new rotation or reach
timeout, their windows will fade in. If the redraw time is quick
enough, the visual appearance will be almost the same as waiting
for all windows because the the duration of rotation animation make
the fade-in animation unnoticeable.

Bug: 178472794
Test: atest DisplayContentTests#testHybridRotationAnimation
Test: Rotate device and check log "Screen frozen for".

Change-Id: I57cce881a2d685a2f1130d7e9a335fd9d86ba360
parent f1c8817e
Loading
Loading
Loading
Loading
+38 −13
Original line number Original line Diff line number Diff line
@@ -516,7 +516,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp


    /** The delay to avoid toggling the animation quickly. */
    /** The delay to avoid toggling the animation quickly. */
    private static final long FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS = 250;
    private static final long FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS = 250;
    private FixedRotationAnimationController mFixedRotationAnimationController;
    private FadeRotationAnimationController mFadeRotationAnimationController;


    final FixedRotationTransitionListener mFixedRotationTransitionListener =
    final FixedRotationTransitionListener mFixedRotationTransitionListener =
            new FixedRotationTransitionListener();
            new FixedRotationTransitionListener();
@@ -1590,8 +1590,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
    }
    }


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


    void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
    void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
@@ -1601,13 +1601,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
    void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
    void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
        if (mFixedRotationLaunchingApp == null && r != null) {
        if (mFixedRotationLaunchingApp == null && r != null) {
            mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
            mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
            startFixedRotationAnimation(
            startFadeRotationAnimation(
                    // Delay the hide animation to avoid blinking by clicking navigation bar that
                    // Delay the hide animation to avoid blinking by clicking navigation bar that
                    // may toggle fixed rotation in a short time.
                    // may toggle fixed rotation in a short time.
                    r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */);
                    r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */);
        } else if (mFixedRotationLaunchingApp != null && r == null) {
        } else if (mFixedRotationLaunchingApp != null && r == null) {
            mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
            mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
            finishFixedRotationAnimationIfPossible();
            finishFadeRotationAnimationIfPossible();
        }
        }
        mFixedRotationLaunchingApp = r;
        mFixedRotationLaunchingApp = r;
    }
    }
@@ -1714,12 +1714,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
     *
     *
     * @return {@code true} if the animation is executed right now.
     * @return {@code true} if the animation is executed right now.
     */
     */
    private boolean startFixedRotationAnimation(boolean shouldDebounce) {
    private boolean startFadeRotationAnimation(boolean shouldDebounce) {
        if (shouldDebounce) {
        if (shouldDebounce) {
            mWmService.mH.postDelayed(() -> {
            mWmService.mH.postDelayed(() -> {
                synchronized (mWmService.mGlobalLock) {
                synchronized (mWmService.mGlobalLock) {
                    if (mFixedRotationLaunchingApp != null
                    if (mFixedRotationLaunchingApp != null
                            && startFixedRotationAnimation(false /* shouldDebounce */)) {
                            && startFadeRotationAnimation(false /* shouldDebounce */)) {
                        // Apply the transaction so the animation leash can take effect immediately.
                        // Apply the transaction so the animation leash can take effect immediately.
                        getPendingTransaction().apply();
                        getPendingTransaction().apply();
                    }
                    }
@@ -1727,23 +1727,41 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
            }, FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS);
            }, FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS);
            return false;
            return false;
        }
        }
        if (mFixedRotationAnimationController == null) {
        if (mFadeRotationAnimationController == null) {
            mFixedRotationAnimationController = new FixedRotationAnimationController(this);
            mFadeRotationAnimationController = new FadeRotationAnimationController(this);
            mFixedRotationAnimationController.hide();
            mFadeRotationAnimationController.hide();
            return true;
            return true;
        }
        }
        return false;
        return false;
    }
    }


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


    /** Shows the given window which may be hidden for screen frozen. */
    void finishFadeRotationAnimation(WindowState w) {
        final FadeRotationAnimationController controller = mFadeRotationAnimationController;
        if (controller != null && controller.show(w.mToken)) {
            mFadeRotationAnimationController = null;
        }
    }

    /** Returns {@code true} if the display should wait for the given window to stop freezing. */
    boolean waitForUnfreeze(WindowState w) {
        if (w.mForceSeamlesslyRotate) {
            // The window should look no different before and after rotation.
            return false;
        }
        final FadeRotationAnimationController controller = mFadeRotationAnimationController;
        return controller == null || !controller.isTargetToken(w.mToken);
    }

    void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
    void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
        if (mFixedRotationLaunchingApp != null) {
        if (mFixedRotationLaunchingApp != null) {
            // The insets state of fixed rotation app is a rotated copy. Make sure the visibilities
            // The insets state of fixed rotation app is a rotated copy. Make sure the visibilities
@@ -2964,6 +2982,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
            mScreenRotationAnimation.kill();
            mScreenRotationAnimation.kill();
        }
        }
        mScreenRotationAnimation = screenRotationAnimation;
        mScreenRotationAnimation = screenRotationAnimation;

        // 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()
                && mFadeRotationAnimationController == null) {
            startFadeRotationAnimation(false /* shouldDebounce */);
        }
    }
    }


    public ScreenRotationAnimation getRotationAnimation() {
    public ScreenRotationAnimation getRotationAnimation() {
+2 −2
Original line number Original line Diff line number Diff line
@@ -626,7 +626,7 @@ public class DisplayRotation {
        }, true /* traverseTopToBottom */);
        }, true /* traverseTopToBottom */);
        mSeamlessRotationCount = 0;
        mSeamlessRotationCount = 0;
        mRotatingSeamlessly = false;
        mRotatingSeamlessly = false;
        mDisplayContent.finishFixedRotationAnimationIfPossible();
        mDisplayContent.finishFadeRotationAnimationIfPossible();
    }
    }


    private void prepareSeamlessRotation() {
    private void prepareSeamlessRotation() {
@@ -717,7 +717,7 @@ public class DisplayRotation {
                    "Performing post-rotate rotation after seamless rotation");
                    "Performing post-rotate rotation after seamless rotation");
            // Finish seamless rotation.
            // Finish seamless rotation.
            mRotatingSeamlessly = false;
            mRotatingSeamlessly = false;
            mDisplayContent.finishFixedRotationAnimationIfPossible();
            mDisplayContent.finishFadeRotationAnimationIfPossible();


            updateRotationAndSendNewConfigIfChanged();
            updateRotationAndSendNewConfigIfChanged();
        }
        }
+135 −0
Original line number Original line 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.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;

import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;

import com.android.internal.R;

import java.util.ArrayList;

/**
 * Controller to fade out and in windows when the display is changing rotation. It can be used for
 * both fixed rotation and normal rotation to hide some non-activity windows. The caller should show
 * the windows until they are drawn with the new rotation.
 */
public class FadeRotationAnimationController extends FadeAnimationController {

    private final ArrayList<WindowToken> mTargetWindowTokens = new ArrayList<>();
    private final WindowManagerService mService;
    /** If non-null, it usually indicates that there will be a screen rotation animation. */
    private final Runnable mFrozenTimeoutRunnable;
    private final WindowToken mNavBarToken;

    public FadeRotationAnimationController(DisplayContent displayContent) {
        super(displayContent);
        mService = displayContent.mWmService;
        mFrozenTimeoutRunnable = mService.mDisplayFrozen ? () -> {
            synchronized (mService.mGlobalLock) {
                displayContent.finishFadeRotationAnimationIfPossible();
                mService.mWindowPlacerLocked.performSurfacePlacement();
            }
        } : null;
        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
        final WindowState navigationBar = displayPolicy.getNavigationBar();
        if (navigationBar != null) {
            mNavBarToken = navigationBar.mToken;
            final RecentsAnimationController controller = mService.getRecentsAnimationController();
            final boolean navBarControlledByRecents =
                    controller != null && controller.isNavigationBarAttachedToApp();
            // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation
            // bar is currently controlled by recents animation.
            if (!displayPolicy.navigationBarCanMove() && !navBarControlledByRecents) {
                mTargetWindowTokens.add(mNavBarToken);
            }
        } else {
            mNavBarToken = null;
        }
        displayContent.forAllWindows(w -> {
            if (w.mActivityRecord == null && w.mHasSurface && !w.mForceSeamlesslyRotate
                    && !w.mIsWallpaper && !w.mIsImWindow && w != navigationBar) {
                mTargetWindowTokens.add(w.mToken);
            }
        }, true /* traverseTopToBottom */);
    }

    /** Applies show animation on the previously hidden window tokens. */
    void show() {
        for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
            final WindowToken windowToken = mTargetWindowTokens.get(i);
            fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
        }
        mTargetWindowTokens.clear();
        if (mFrozenTimeoutRunnable != null) {
            mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
        }
    }

    /**
     * Returns {@code true} if all target windows are shown. It only takes effects if this
     * controller is created for normal rotation.
     */
    boolean show(WindowToken token) {
        if (mFrozenTimeoutRunnable != null && mTargetWindowTokens.remove(token)) {
            fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
            if (mTargetWindowTokens.isEmpty()) {
                mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
                return true;
            }
        }
        return false;
    }

    /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
    void hide() {
        for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
            final WindowToken windowToken = mTargetWindowTokens.get(i);
            fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
        }
        if (mFrozenTimeoutRunnable != null) {
            mService.mH.postDelayed(mFrozenTimeoutRunnable,
                    WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
        }
    }

    /** Returns {@code true} if the window is handled by this controller. */
    boolean isTargetToken(WindowToken token) {
        return token == mNavBarToken || mTargetWindowTokens.contains(token);
    }

    @Override
    public Animation getFadeInAnimation() {
        if (mFrozenTimeoutRunnable != null) {
            // Use a shorter animation so it is easier to align with screen rotation animation.
            return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
        }
        return super.getFadeInAnimation();
    }

    @Override
    public Animation getFadeOutAnimation() {
        if (mFrozenTimeoutRunnable != null) {
            // Hide the window immediately because screen should have been covered by screenshot.
            return new AlphaAnimation(0 /* fromAlpha */, 0 /* toAlpha */);
        }
        return super.getFadeOutAnimation();
    }
}
+0 −76
Original line number Original line 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.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;

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 extends FadeAnimationController {

    private final WindowState mStatusBar;
    private final WindowState mNavigationBar;
    private final ArrayList<WindowToken> mAnimatedWindowToken = new ArrayList<>(2);

    public FixedRotationAnimationController(DisplayContent displayContent) {
        super(displayContent);
        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
        mStatusBar = displayPolicy.getStatusBar();

        final RecentsAnimationController controller =
                displayContent.mWmService.getRecentsAnimationController();
        final boolean navBarControlledByRecents =
                controller != null && controller.isNavigationBarAttachedToApp();
        // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation bar
        // is currently controlled by recents animation.
        mNavigationBar = !displayPolicy.navigationBarCanMove()
                && !navBarControlledByRecents ? 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, ANIMATION_TYPE_FIXED_TRANSFORM);
        }
    }

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

    @Override
    public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
        super.fadeWindowToken(show, windowToken, animationType);
        mAnimatedWindowToken.add(windowToken);
    }
}
+1 −1
Original line number Original line Diff line number Diff line
@@ -77,7 +77,7 @@ class NonAppWindowAnimationAdapter implements AnimationAdapter {
            final boolean shouldAttachNavBarToApp =
            final boolean shouldAttachNavBarToApp =
                    displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                    displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                            && service.getRecentsAnimationController() == null
                            && service.getRecentsAnimationController() == null
                            && displayContent.getFixedRotationAnimationController() == null;
                            && displayContent.getFadeRotationAnimationController() == null;
            if (shouldAttachNavBarToApp) {
            if (shouldAttachNavBarToApp) {
                startNavigationBarWindowAnimation(
                startNavigationBarWindowAnimation(
                        displayContent, durationHint, statusBarTransitionDelay, targets,
                        displayContent, durationHint, statusBarTransitionDelay, targets,
Loading