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

Commit 83537a70 authored by Vishnu Nair's avatar Vishnu Nair
Browse files

WM: correctly draw the rounded corner / cutout overlay during rotation

When we freeze the screen, we really don't want the overlay to appear
on the screenshot - otherwise this will lead to it rotating with the
screen content. This means the overlay currently disappears during the
transition. We cannot just draw it over the screenshot, because it
might be in inconsistent state.

We fix this by temporarily undoing the effects of the screen rotation
transform on the overlay's window token. Then, once the window has
performed relayout and is redrawn in the new orientation, we switch to
that representation. This is mostly seamless rotation, with the
difference that we force it always, and it must also work for 180
degree rotation (which regular seamless rotation does not).

Also move the rounded corner overlay from the display overlay layer
to the root of the hierarchy such that it can draw over the screen
off animation's ColorLayer.

Cherry picked from ag/4226061

Bug: 111504081
Test: Enable display cutout overlay, rotate phone to all orientations, ensure that emulated display cutout never flashes or disappears.
Test: atest CoordinateTransformsTest
Test: atest FlickerTests
Change-Id: I8c538b4a5402560c63578c954e8ee7d371079d89
parent 5fc7e233
Loading
Loading
Loading
Loading
+52 −5
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -97,6 +98,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
    private DisplayCutoutView mCutoutTop;
    private DisplayCutoutView mCutoutBottom;
    private SecureSetting mColorInversionSetting;
    private boolean mPendingRotationChange;

    @Override
    public void start() {
@@ -130,6 +132,21 @@ public class ScreenDecorations extends SystemUI implements Tunable {

            @Override
            public void onDisplayChanged(int displayId) {
                if ((hasRoundedCorners() || shouldDrawCutout()) &&
                        mRotation != RotationUtils.getExactRotation(mContext)) {
                    // We cannot immediately update the orientation. Otherwise
                    // WindowManager is still deferring layout until it has finished dispatching
                    // the config changes, which may cause divergence between what we draw
                    // (new orientation), and where we are placed on the screen (old orientation).
                    // Instead we wait until either:
                    // - we are trying to redraw. This because WM resized our window and told us to.
                    // - the config change has been dispatched, so WM is no longer deferring layout.
                    mPendingRotationChange = true;
                    mOverlay.getViewTreeObserver().addOnPreDrawListener(
                            new RestartingPreDrawListener(mOverlay));
                    mBottomOverlay.getViewTreeObserver().addOnPreDrawListener(
                            new RestartingPreDrawListener(mBottomOverlay));
                }
                updateOrientation();
            }
        };
@@ -144,12 +161,12 @@ public class ScreenDecorations extends SystemUI implements Tunable {
        mOverlay = LayoutInflater.from(mContext)
                .inflate(R.layout.rounded_corners, null);
        mCutoutTop = new DisplayCutoutView(mContext, true,
                this::updateWindowVisibilities);
                this::updateWindowVisibilities, this);
        ((ViewGroup)mOverlay).addView(mCutoutTop);
        mBottomOverlay = LayoutInflater.from(mContext)
                .inflate(R.layout.rounded_corners, null);
        mCutoutBottom = new DisplayCutoutView(mContext, false,
                this::updateWindowVisibilities);
                this::updateWindowVisibilities, this);
        ((ViewGroup)mBottomOverlay).addView(mCutoutBottom);

        mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
@@ -229,6 +246,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        mPendingRotationChange = false;
        updateOrientation();
        if (shouldDrawCutout() && mOverlay == null) {
            setupDecorations();
@@ -236,6 +254,9 @@ public class ScreenDecorations extends SystemUI implements Tunable {
    }

    protected void updateOrientation() {
        if (mPendingRotationChange) {
            return;
        }
        int newRotation = RotationUtils.getExactRotation(mContext);
        if (newRotation != mRotation) {
            mRotation = newRotation;
@@ -451,15 +472,17 @@ public class ScreenDecorations extends SystemUI implements Tunable {
        private final int[] mLocation = new int[2];
        private final boolean mInitialStart;
        private final Runnable mVisibilityChangedListener;
        private final ScreenDecorations mDecorations;
        private int mColor = Color.BLACK;
        private boolean mStart;
        private int mRotation;

        public DisplayCutoutView(Context context, boolean start,
                Runnable visibilityChangedListener) {
                Runnable visibilityChangedListener, ScreenDecorations decorations) {
            super(context);
            mInitialStart = start;
            mVisibilityChangedListener = visibilityChangedListener;
            mDecorations = decorations;
            setId(R.id.display_cutout);
        }

@@ -522,10 +545,10 @@ public class ScreenDecorations extends SystemUI implements Tunable {
        }

        private void update() {
            mStart = isStart();
            if (!isAttachedToWindow()) {
            if (!isAttachedToWindow() || mDecorations.mPendingRotationChange) {
                return;
            }
            mStart = isStart();
            requestLayout();
            getDisplay().getDisplayInfo(mInfo);
            mBounds.setEmpty();
@@ -688,4 +711,28 @@ public class ScreenDecorations extends SystemUI implements Tunable {
        return rotation == RotationUtils.ROTATION_LANDSCAPE || rotation ==
                RotationUtils.ROTATION_SEASCAPE;
    }

    /**
     * A pre-draw listener, that cancels the draw and restarts the traversal with the updated
     * window attributes.
     */
    private class RestartingPreDrawListener implements ViewTreeObserver.OnPreDrawListener {

        private final View mView;

        private RestartingPreDrawListener(View view) {
            mView = view;
        }

        @Override
        public boolean onPreDraw() {
            mPendingRotationChange = false;
            mView.getViewTreeObserver().removeOnPreDrawListener(this);
            // This changes the window attributes - we need to restart the traversal for them to
            // take effect.
            updateOrientation();
            mView.invalidate();
            return false;
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import android.view.SurfaceSession;
import libcore.io.Streams;

import com.android.server.LocalServices;
import com.android.server.policy.WindowManagerPolicy;

/**
 * <p>
@@ -63,7 +64,7 @@ final class ColorFade {

    // The layer for the electron beam surface.
    // This is currently hardcoded to be one layer above the boot animation.
    private static final int COLOR_FADE_LAYER = 0x40000001;
    private static final int COLOR_FADE_LAYER = WindowManagerPolicy.COLOR_FADE_LAYER;

    // The number of frames to draw when preparing the animation so that it will
    // be ready to run smoothly.  We use 3 frames because we are triple-buffered.
+2 −0
Original line number Diff line number Diff line
@@ -156,6 +156,8 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
    int FINISH_LAYOUT_REDO_WALLPAPER = 0x0004;
    /** Need to recompute animations */
    int FINISH_LAYOUT_REDO_ANIM = 0x0008;
    /** Layer for the screen off animation */
    int COLOR_FADE_LAYER = 0x40000001;

    /**
     * Register shortcuts for window manager to dispatch.
+22 −0
Original line number Diff line number Diff line
@@ -1136,6 +1136,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
            }
        }

        forAllWindows(w -> {
            w.forceSeamlesslyRotateIfAllowed(oldRotation, rotation);
        }, true /* traverseTopToBottom */);

        // TODO(b/111504081): Consolidate seamless rotation logic.
        if (rotateSeamlessly) {
            seamlesslyRotate(getPendingTransaction(), oldRotation, rotation);
        }
@@ -3766,6 +3771,19 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
            super(name, service);
        }

        @Override
        SurfaceControl.Builder makeChildSurface(WindowContainer child) {
            final SurfaceControl.Builder builder = super.makeChildSurface(child);
            if (child instanceof WindowToken && ((WindowToken) child).mRoundedCornerOverlay) {
                // To draw above the ColorFade layer during the screen off transition, the
                // rounded corner overlays need to be at the root of the surface hierarchy.
                // TODO: move the ColorLayer into the display overlay layer such that this is not
                // necessary anymore.
                builder.setParent(null);
            }
            return builder;
        }

        @Override
        void assignChildLayers(SurfaceControl.Transaction t) {
            assignChildLayers(t, null /* imeContainer */);
@@ -3782,6 +3800,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
                    wt.assignRelativeLayer(t, mTaskStackContainers.getSplitScreenDividerAnchor(), 1);
                    continue;
                }
                if (wt.mRoundedCornerOverlay) {
                    wt.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1);
                    continue;
                }
                wt.assignLayer(t, j);
                wt.assignChildLayers(t);

+78 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;

import android.graphics.Matrix;
import android.view.DisplayInfo;

import com.android.server.wm.utils.CoordinateTransforms;

/**
 * Helper class for forced seamless rotation.
 *
 * Works by transforming the window token back into the old display rotation.
 *
 * Uses deferTransactionUntil instead of latching on the buffer size to allow for seamless 180
 * degree rotations.
 * TODO(b/111504081): Consolidate seamless rotation logic.
 */
public class ForcedSeamlessRotator {

    private final Matrix mTransform = new Matrix();
    private final float[] mFloat9 = new float[9];

    public ForcedSeamlessRotator(int oldRotation, int newRotation, DisplayInfo info) {
        final boolean flipped = info.rotation == ROTATION_90 || info.rotation == ROTATION_270;
        final int h = flipped ? info.logicalWidth : info.logicalHeight;
        final int w = flipped ? info.logicalHeight : info.logicalWidth;

        final Matrix tmp = new Matrix();
        CoordinateTransforms.transformLogicalToPhysicalCoordinates(oldRotation, w, h, mTransform);
        CoordinateTransforms.transformPhysicalToLogicalCoordinates(newRotation, w, h, tmp);
        mTransform.postConcat(tmp);
    }

    /**
     * Applies a transform to the window token's surface that undoes the effect of the global
     * display rotation.
     */
    public void unrotate(WindowToken token) {
        token.getPendingTransaction().setMatrix(token.getSurfaceControl(), mTransform, mFloat9);
    }

    /**
     * Removes the transform to the window token's surface that undoes the effect of the global
     * display rotation.
     *
     * Removing the transform and the result of the WindowState's layout are both tied to the
     * WindowState's next frame, such that they apply at the same time the client draws the
     * window in the new orientation.
     */
    public void finish(WindowToken token, WindowState win) {
        mTransform.reset();
        token.getPendingTransaction().setMatrix(token.mSurfaceControl, mTransform, mFloat9);
        token.getPendingTransaction().deferTransactionUntil(token.mSurfaceControl,
                win.mWinAnimator.mSurfaceController.mSurfaceControl.getHandle(),
                win.getFrameNumber());
        win.getPendingTransaction().deferTransactionUntil(win.mSurfaceControl,
                win.mWinAnimator.mSurfaceController.mSurfaceControl.getHandle(),
                win.getFrameNumber());
    }
}
Loading