From 83537a7010acad9af8df39c7f98fdbd1550aadb1 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 19 Jul 2018 21:27:48 -0700 Subject: [PATCH] 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 --- .../android/systemui/ScreenDecorations.java | 57 ++++++++++++-- .../com/android/server/display/ColorFade.java | 3 +- .../server/policy/WindowManagerPolicy.java | 2 + .../com/android/server/wm/DisplayContent.java | 22 ++++++ .../server/wm/ForcedSeamlessRotator.java | 78 +++++++++++++++++++ .../server/wm/WindowManagerService.java | 7 ++ .../com/android/server/wm/WindowState.java | 26 ++++++- .../server/wm/WindowStateAnimator.java | 17 +++- .../com/android/server/wm/WindowToken.java | 6 -- .../wm/utils/CoordinateTransformsTest.java | 2 +- 10 files changed, 203 insertions(+), 17 deletions(-) create mode 100644 services/core/java/com/android/server/wm/ForcedSeamlessRotator.java diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index bbbc71fe8c1c9..520e40aa1e56c 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -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; + } + } } diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index 4f53ed49002b8..33525fdc52d2a 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -46,6 +46,7 @@ import android.view.SurfaceSession; import libcore.io.Streams; import com.android.server.LocalServices; +import com.android.server.policy.WindowManagerPolicy; /** *

@@ -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. diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 7ea620039026a..3b568e895e466 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -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. diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index efe4d6f48da10..fb090929c00c9 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1136,6 +1136,11 @@ class DisplayContent extends WindowContainer { + 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 implements WindowManagerP private boolean mDragResizing; private boolean mDragResizingChangeReported = true; private int mResizeMode; + /** + * Special mode that is intended only for the rounded corner overlay: during rotation + * transition, we un-rotate the window token such that the window appears as it did before the + * rotation. + * TODO(b/111504081): Consolidate seamless rotation logic. + */ + final boolean mForceSeamlesslyRotate; + ForcedSeamlessRotator mPendingForcedSeamlessRotate; private RemoteCallbackList mFocusCallbacks; @@ -671,6 +679,14 @@ class WindowState extends WindowContainer implements WindowManagerP private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f; + void forceSeamlesslyRotateIfAllowed(int oldRotation, int rotation) { + if (mForceSeamlesslyRotate) { + mPendingForcedSeamlessRotate = new ForcedSeamlessRotator( + oldRotation, rotation, getDisplayInfo()); + mPendingForcedSeamlessRotate.unrotate(this.mToken); + } + } + interface PowerManagerWrapper { void wakeUp(long time, String reason); @@ -717,6 +733,7 @@ class WindowState extends WindowContainer implements WindowManagerP mSeq = seq; mEnforceSizeCompat = (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0; mPowerManagerWrapper = powerManagerWrapper; + mForceSeamlesslyRotate = token.mRoundedCornerOverlay; if (localLOGV) Slog.v( TAG, "Window " + this + " client=" + c.asBinder() + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a); @@ -4710,7 +4727,10 @@ class WindowState extends WindowContainer implements WindowManagerP transformFrameToSurfacePosition(mFrame.left, mFrame.top, mSurfacePosition); - if (!mSurfaceAnimator.hasLeash() && !mLastSurfacePosition.equals(mSurfacePosition)) { + // Freeze position while we're unrotated, so the surface remains at the position it was + // prior to the rotation. + if (!mSurfaceAnimator.hasLeash() && mPendingForcedSeamlessRotate == null && + !mLastSurfacePosition.equals(mSurfacePosition)) { t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y); mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y); if (surfaceInsetsChanging() && mWinAnimator.hasSurface()) { @@ -4865,7 +4885,9 @@ class WindowState extends WindowContainer implements WindowManagerP @Override void seamlesslyRotate(Transaction t, int oldRotation, int newRotation) { - if (!isVisibleNow() || mIsWallpaper) { + // Invisible windows, the wallpaper, and force seamlessly rotated windows do not participate + // in the regular seamless rotation animation. + if (!isVisibleNow() || mIsWallpaper || mForceSeamlesslyRotate) { return; } final Matrix transform = mTmpMatrix; diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 14e0e13414a94..1673270a15090 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -686,8 +686,12 @@ class WindowStateAnimator { final int displayId = mWin.getDisplayId(); final ScreenRotationAnimation screenRotationAnimation = mAnimator.getScreenRotationAnimationLocked(displayId); - final boolean screenAnimation = - screenRotationAnimation != null && screenRotationAnimation.isAnimating(); + // TODO(b/111504081): Consolidate seamless rotation logic. + final boolean windowParticipatesInScreenRotationAnimation = + !mWin.mForceSeamlesslyRotate; + final boolean screenAnimation = screenRotationAnimation != null + && screenRotationAnimation.isAnimating() + && windowParticipatesInScreenRotationAnimation; if (screenAnimation) { // cache often used attributes locally @@ -799,6 +803,13 @@ class WindowStateAnimator { return false; } + // During forced seamless rotation, the surface bounds get updated with the crop in the + // new rotation, which is not compatible with showing the surface in the old rotation. + // To work around that we disable cropping for such windows, as it is not necessary anyways. + if (w.mForceSeamlesslyRotate) { + return false; + } + // If we're animating, the wallpaper should only // be updated at the end of the animation. if (w.mAttrs.type == TYPE_WALLPAPER) { @@ -1498,6 +1509,8 @@ class WindowStateAnimator { } } + // TODO(b/111504081): Consolidate seamless rotation logic. + @Deprecated void seamlesslyRotate(SurfaceControl.Transaction t, int oldRotation, int newRotation) { final WindowState w = mWin; diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index b97460ae9eb83..e411c0adc75fa 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -270,12 +270,6 @@ class WindowToken extends WindowContainer { dc.reParentWindowToken(this); mDisplayContent = dc; - // The rounded corner overlay should not be rotated. We ensure that by moving it outside - // the windowing layer. - if (mRoundedCornerOverlay) { - mDisplayContent.reparentToOverlay(mPendingTransaction, mSurfaceControl); - } - // TODO(b/36740756): One day this should perhaps be hooked // up with goodToGo, so we don't move a window // to another display before the window behind diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java index 361522cfc8801..f82b01224f969 100644 --- a/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java @@ -99,7 +99,7 @@ public class CoordinateTransformsTest { checkPoint(0, W).transformsTo(0, 0); checkPoint(H, 0).transformsTo(W, H); -} + } @Test public void transformLogicalToPhysicalCoordinates_rot180() {