From 41f7e9d10306603c7a4410938e77418f13f6e939 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Thu, 7 Jun 2018 15:29:34 +0200 Subject: [PATCH 1/2] WM: Fix seamless rotation With the introduction of the surface hierarchy, the seamless rotation behavior in WSA is no longer correct: it also applies the WindowState's offset, which leads to that being applied twice. Instead of doing that, we simply rotate the WSA surface within the place that WindowState dictates now. Finally, the location of the WindowState itself also needs to be transformed into the new orientation. Fixes: 109927566 Test: atest CoordinateTransformsTest Test: atest 'WindowStateTests#testSeamlesslyRotateWindow' Change-Id: I9fb27a0a8a2bddc6ec88a4fcce6d6ea00929fb91 --- .../com/android/server/wm/DisplayContent.java | 5 +- .../android/server/wm/WindowContainer.java | 14 ++ .../com/android/server/wm/WindowState.java | 28 +++- .../server/wm/WindowStateAnimator.java | 36 +---- .../server/wm/utils/CoordinateTransforms.java | 93 ++++++++++++ .../android/server/wm/WindowStateTests.java | 32 +++++ .../wm/utils/CoordinateTransformsTest.java | 133 ++++++++++++++++-- 7 files changed, 292 insertions(+), 49 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index b0e6208fdff3e..2857603e372cb 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1107,10 +1107,7 @@ class DisplayContent extends WindowContainer { - w.mWinAnimator.seamlesslyRotateWindow(getPendingTransaction(), - oldRotation, rotation); - }, true /* traverseTopToBottom */); + seamlesslyRotate(getPendingTransaction(), oldRotation, rotation); } mService.mDisplayManagerInternal.performTraversal(getPendingTransaction()); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 19c5a3d6452a9..8fe7063c815b5 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -735,6 +735,20 @@ class WindowContainer extends ConfigurationContainer< return candidate; } + /** + * Seamlessly rotates the container, by recomputing the location in the new + * rotation, and rotating buffers until they are updated for the new rotation. + * + * @param t the transaction to perform the seamless rotation in + * @param oldRotation the rotation we are rotating from + * @param newRotation the rotation we are rotating to + */ + void seamlesslyRotate(Transaction t, int oldRotation, int newRotation) { + for (int i = mChildren.size() - 1; i >= 0; --i) { + mChildren.get(i).seamlesslyRotate(t, oldRotation, newRotation); + } + } + /** * Returns true if this container is opaque and fills all the space made available by its parent * container. diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index bee70a0119459..83a63154eb19e 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -150,6 +150,8 @@ import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY; import static com.android.server.wm.WindowStateProto.VISIBLE_FRAME; import static com.android.server.wm.WindowStateProto.VISIBLE_INSETS; import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER; +import static com.android.server.wm.utils.CoordinateTransforms.transformRect; +import static com.android.server.wm.utils.CoordinateTransforms.transformToRotation; import android.annotation.CallSuper; import android.app.AppOpsManager; @@ -1811,7 +1813,8 @@ class WindowState extends WindowContainer implements WindowManagerP && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0 && !isDragResizing() && !adjustedForMinimizedDockOrIme && getWindowConfiguration().hasMovementAnimations() - && !mWinAnimator.mLastHidden) { + && !mWinAnimator.mLastHidden + && !mSeamlesslyRotated) { startMoveAnimation(left, top); } @@ -4850,6 +4853,29 @@ class WindowState extends WindowContainer implements WindowManagerP mFrameNumber = frameNumber; } + @Override + void seamlesslyRotate(Transaction t, int oldRotation, int newRotation) { + if (!isVisibleNow() || mIsWallpaper) { + return; + } + final Matrix transform = mTmpMatrix; + + mService.markForSeamlessRotation(this, true); + + // We rotated the screen, but have not performed a new layout pass yet. In the mean time, + // we recompute the coordinates of mFrame in the new orientation, so the surface can be + // properly placed. + transformToRotation(oldRotation, newRotation, getDisplayInfo(), transform); + transformRect(transform, mFrame, null /* tmpRectF */); + + updateSurfacePosition(t); + mWinAnimator.seamlesslyRotate(t, oldRotation, newRotation); + + // Dispatch to children only after mFrame has been updated, as it's needed in the + // child's updateSurfacePosition. + super.seamlesslyRotate(t, oldRotation, newRotation); + } + private final class MoveAnimationSpec implements AnimationSpec { private final long mDuration; diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 3eef12540e7f8..989d6941e16fb 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -46,13 +46,13 @@ import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE; import static com.android.server.wm.WindowStateAnimatorProto.LAST_CLIP_RECT; import static com.android.server.wm.WindowStateAnimatorProto.SURFACE; import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT; +import static com.android.server.wm.utils.CoordinateTransforms.transformToRotation; import android.content.Context; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.Region; import android.os.Debug; import android.os.Trace; @@ -1492,40 +1492,14 @@ class WindowStateAnimator { } } - void seamlesslyRotateWindow(SurfaceControl.Transaction t, - int oldRotation, int newRotation) { + void seamlesslyRotate(SurfaceControl.Transaction t, int oldRotation, int newRotation) { final WindowState w = mWin; - if (!w.isVisibleNow() || w.mIsWallpaper) { - return; - } - final Rect cropRect = mService.mTmpRect; - final Rect displayRect = mService.mTmpRect2; - final RectF frameRect = mService.mTmpRectF; + // We rotated the screen, but have not received a new buffer with the correct size yet. In + // the mean time, we rotate the buffer we have to the new orientation. final Matrix transform = mService.mTmpTransform; - - final float x = w.mFrame.left; - final float y = w.mFrame.top; - final float width = w.mFrame.width(); - final float height = w.mFrame.height(); - - mService.getDefaultDisplayContentLocked().getBounds(displayRect); - final float displayWidth = displayRect.width(); - final float displayHeight = displayRect.height(); - - // Compute a transform matrix to undo the coordinate space transformation, - // and present the window at the same physical position it previously occupied. - final int deltaRotation = DisplayContent.deltaRotation(newRotation, oldRotation); - DisplayContent.createRotationMatrix(deltaRotation, x, y, displayWidth, displayHeight, + transformToRotation(oldRotation, newRotation, w.mFrame.width(), w.mFrame.height(), transform); - - // We just need to apply a rotation matrix to the window. For example - // if we have a portrait window and rotate to landscape, the window is still portrait - // and now extends off the bottom of the screen (and only halfway across). Essentially we - // apply a transform to display the current buffer at it's old position - // (in the new coordinate space). We then freeze layer updates until the resize - // occurs, at which point we undo, them. - mService.markForSeamlessRotation(w, true); transform.getValues(mService.mTmpFloats); float DsDx = mService.mTmpFloats[Matrix.MSCALE_X]; diff --git a/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java index 09d7b5de1caf9..a2f37a56598dd 100644 --- a/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java +++ b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java @@ -22,7 +22,11 @@ import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import android.annotation.Dimension; +import android.annotation.Nullable; import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.view.DisplayInfo; import android.view.Surface.Rotation; public class CoordinateTransforms { @@ -59,4 +63,93 @@ public class CoordinateTransforms { throw new IllegalArgumentException("Unknown rotation: " + rotation); } } + + /** + * Sets a matrix such that given a rotation, it transforms that rotation's logical coordinates + * to physical coordinates. + * + * @param rotation the rotation to which the matrix should transform + * @param out the matrix to be set + */ + public static void transformLogicalToPhysicalCoordinates(@Rotation int rotation, + @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) { + switch (rotation) { + case ROTATION_0: + out.reset(); + break; + case ROTATION_90: + out.setRotate(90); + out.preTranslate(0, -physicalWidth); + break; + case ROTATION_180: + out.setRotate(180); + out.preTranslate(-physicalWidth, -physicalHeight); + break; + case ROTATION_270: + out.setRotate(270); + out.preTranslate(-physicalHeight, 0); + break; + default: + throw new IllegalArgumentException("Unknown rotation: " + rotation); + } + } + + /** + * Sets a matrix such that given a two rotations, that it transforms coordinates given in the + * old rotation to coordinates that refer to the same physical location in the new rotation. + * + * @param oldRotation the rotation to transform from + * @param newRotation the rotation to transform to + * @param info the display info + * @param out a matrix that will be set to the transform + */ + public static void transformToRotation(@Rotation int oldRotation, + @Rotation int newRotation, DisplayInfo info, Matrix out) { + 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(); + transformLogicalToPhysicalCoordinates(oldRotation, w, h, out); + transformPhysicalToLogicalCoordinates(newRotation, w, h, tmp); + out.postConcat(tmp); + } + + /** + * Sets a matrix such that given a two rotations, that it transforms coordinates given in the + * old rotation to coordinates that refer to the same physical location in the new rotation. + * + * @param oldRotation the rotation to transform from + * @param newRotation the rotation to transform to + * @param newWidth the width of the area to transform, in the new rotation + * @param newHeight the height of the area to transform, in the new rotation + * @param out a matrix that will be set to the transform + */ + public static void transformToRotation(@Rotation int oldRotation, + @Rotation int newRotation, int newWidth, int newHeight, Matrix out) { + final boolean flipped = newRotation == ROTATION_90 || newRotation == ROTATION_270; + final int h = flipped ? newWidth : newHeight; + final int w = flipped ? newHeight : newWidth; + + final Matrix tmp = new Matrix(); + transformLogicalToPhysicalCoordinates(oldRotation, w, h, out); + transformPhysicalToLogicalCoordinates(newRotation, w, h, tmp); + out.postConcat(tmp); + } + + /** + * Transforms a rect using a transformation matrix + * + * @param transform the transformation to apply to the rect + * @param inOutRect the rect to transform + * @param tmp a temporary value, if null the function will allocate its own. + */ + public static void transformRect(Matrix transform, Rect inOutRect, @Nullable RectF tmp) { + if (tmp == null) { + tmp = new RectF(); + } + tmp.set(inOutRect); + transform.mapRect(tmp); + inOutRect.set((int) tmp.left, (int) tmp.top, (int) tmp.right, (int) tmp.bottom); + } } diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java index 85e846db60b74..9f113ad3137eb 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import android.graphics.Rect; +import android.view.SurfaceControl; import android.view.WindowManager; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,6 +31,8 @@ import java.util.LinkedList; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.hardware.camera2.params.OutputConfiguration.ROTATION_90; +import static android.view.Surface.ROTATION_0; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; @@ -48,8 +52,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; @@ -349,6 +355,32 @@ public class WindowStateTests extends WindowTestsBase { assertThat(app.getDisplayId(), is(mDisplayContent.getDisplayId())); } + @Test + public void testSeamlesslyRotateWindow() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + + app.mHasSurface = true; + app.mSurfaceControl = mock(SurfaceControl.class); + app.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class); + try { + app.mFrame.set(10, 20, 60, 80); + + app.seamlesslyRotate(t, ROTATION_0, ROTATION_90); + + assertTrue(app.mSeamlesslyRotated); + assertEquals(new Rect(20, mDisplayInfo.logicalWidth - 60, + 80, mDisplayInfo.logicalWidth - 10), app.mFrame); + + verify(t).setPosition(app.mSurfaceControl, app.mFrame.left, app.mFrame.top); + verify(app.mWinAnimator.mSurfaceController).setPosition(t, 0, 50, false); + verify(app.mWinAnimator.mSurfaceController).setMatrix(t, 0, -1, 1, 0, false); + } finally { + app.mSurfaceControl = null; + app.mHasSurface = false; + } + } + private void testPrepareWindowToDisplayDuringRelayout(boolean wasVisible) { reset(mPowerManagerWrapper); final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); 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 40a10e04c893a..361522cfc8801 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 @@ -21,14 +21,19 @@ import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import static com.android.server.wm.utils.CoordinateTransforms.transformLogicalToPhysicalCoordinates; import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates; + +import static com.android.server.wm.utils.CoordinateTransforms.transformToRotation; + import static org.hamcrest.Matchers.is; import static org.junit.Assert.*; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.PointF; +import android.view.DisplayInfo; import org.junit.Before; import org.junit.Rule; @@ -41,6 +46,7 @@ public class CoordinateTransformsTest { private static final int H = 400; private final Matrix mMatrix = new Matrix(); + private final Matrix mMatrix2 = new Matrix(); @Rule public final ErrorCollector mErrorCollector = new ErrorCollector(); @@ -48,39 +54,140 @@ public class CoordinateTransformsTest { @Before public void setUp() throws Exception { mMatrix.setTranslate(0xdeadbeef, 0xdeadbeef); + mMatrix2.setTranslate(0xbeefdead, 0xbeefdead); } @Test - public void transformPhysicalToLogicalCoordinates_rot0() throws Exception { + public void transformPhysicalToLogicalCoordinates_rot0() { transformPhysicalToLogicalCoordinates(ROTATION_0, W, H, mMatrix); assertThat(mMatrix, is(Matrix.IDENTITY_MATRIX)); } @Test - public void transformPhysicalToLogicalCoordinates_rot90() throws Exception { + public void transformPhysicalToLogicalCoordinates_rot90() { transformPhysicalToLogicalCoordinates(ROTATION_90, W, H, mMatrix); - checkDevicePoint(0, 0).mapsToLogicalPoint(0, W); - checkDevicePoint(W, H).mapsToLogicalPoint(H, 0); + checkPoint(0, 0).transformsTo(0, W); + checkPoint(W, H).transformsTo(H, 0); } @Test - public void transformPhysicalToLogicalCoordinates_rot180() throws Exception { + public void transformPhysicalToLogicalCoordinates_rot180() { transformPhysicalToLogicalCoordinates(ROTATION_180, W, H, mMatrix); - checkDevicePoint(0, 0).mapsToLogicalPoint(W, H); - checkDevicePoint(W, H).mapsToLogicalPoint(0, 0); + checkPoint(0, 0).transformsTo(W, H); + checkPoint(W, H).transformsTo(0, 0); } @Test - public void transformPhysicalToLogicalCoordinates_rot270() throws Exception { + public void transformPhysicalToLogicalCoordinates_rot270() { transformPhysicalToLogicalCoordinates(ROTATION_270, W, H, mMatrix); - checkDevicePoint(0, 0).mapsToLogicalPoint(H, 0); - checkDevicePoint(W, H).mapsToLogicalPoint(0, W); + checkPoint(0, 0).transformsTo(H, 0); + checkPoint(W, H).transformsTo(0, W); } - private DevicePointAssertable checkDevicePoint(int x, int y) { + @Test + public void transformLogicalToPhysicalCoordinates_rot0() { + transformLogicalToPhysicalCoordinates(ROTATION_0, W, H, mMatrix); + assertThat(mMatrix, is(Matrix.IDENTITY_MATRIX)); + } + + @Test + public void transformLogicalToPhysicalCoordinates_rot90() { + transformLogicalToPhysicalCoordinates(ROTATION_90, W, H, mMatrix); + + checkPoint(0, W).transformsTo(0, 0); + checkPoint(H, 0).transformsTo(W, H); +} + + @Test + public void transformLogicalToPhysicalCoordinates_rot180() { + transformLogicalToPhysicalCoordinates(ROTATION_180, W, H, mMatrix); + + checkPoint(W, H).transformsTo(0, 0); + checkPoint(0, 0).transformsTo(W, H); + } + + @Test + public void transformLogicalToPhysicalCoordinates_rot270() { + transformLogicalToPhysicalCoordinates(ROTATION_270, W, H, mMatrix); + + checkPoint(H, 0).transformsTo(0, 0); + checkPoint(0, W).transformsTo(W, H); + } + + @Test + public void transformLogicalToPhysicalCoordinatesIsInverse_rot0() { + transformLogicalToPhysicalCoordinates(ROTATION_0, W, H, mMatrix); + transformPhysicalToLogicalCoordinates(ROTATION_0, W, H, mMatrix2); + + assertMatricesAreInverses(mMatrix, mMatrix2); + } + + @Test + public void transformLogicalToPhysicalCoordinatesIsInverse_rot90() { + transformLogicalToPhysicalCoordinates(ROTATION_90, W, H, mMatrix); + transformPhysicalToLogicalCoordinates(ROTATION_90, W, H, mMatrix2); + + assertMatricesAreInverses(mMatrix, mMatrix2); + } + + @Test + public void transformLogicalToPhysicalCoordinatesIsInverse_rot180() { + transformLogicalToPhysicalCoordinates(ROTATION_180, W, H, mMatrix); + transformPhysicalToLogicalCoordinates(ROTATION_180, W, H, mMatrix2); + + assertMatricesAreInverses(mMatrix, mMatrix2); + } + + @Test + public void transformLogicalToPhysicalCoordinatesIsInverse_rot270() { + transformLogicalToPhysicalCoordinates(ROTATION_270, W, H, mMatrix); + transformPhysicalToLogicalCoordinates(ROTATION_270, W, H, mMatrix2); + + assertMatricesAreInverses(mMatrix, mMatrix2); + } + + @Test + public void transformBetweenRotations_rot180_rot270() { + // W,H are flipped, because they need to be given in the new orientation, i.e. ROT_270. + transformToRotation(ROTATION_180, ROTATION_270, H, W, mMatrix); + + checkPoint(0, 0).transformsTo(0, W); + checkPoint(W, H).transformsTo(H, 0); + } + + @Test + public void transformBetweenRotations_rot90_rot0() { + transformToRotation(ROTATION_180, ROTATION_270, W, H, mMatrix); + + checkPoint(0, 0).transformsTo(0, H); + // H,W is bottom right in ROT_90 + checkPoint(H, W).transformsTo(W, 0); + } + + @Test + public void transformBetweenRotations_displayInfo() { + final DisplayInfo di = new DisplayInfo(); + di.rotation = ROTATION_90; + di.logicalWidth = H; // dimensions are flipped in ROT_90 + di.logicalHeight = W; + transformToRotation(ROTATION_180, ROTATION_270, di, mMatrix); + + // W,H are flipped, because they need to be given in the new orientation, i.e. ROT_270. + transformToRotation(ROTATION_180, ROTATION_270, H, W, mMatrix2); + + assertEquals(mMatrix2, mMatrix); + } + + private void assertMatricesAreInverses(Matrix matrix, Matrix matrix2) { + final Matrix concat = new Matrix(); + concat.setConcat(matrix, matrix2); + assertTrue("expected identity, but was: " + concat, concat.isIdentity()); + } + + private TransformPointAssertable checkPoint(int x, int y) { final Point devicePoint = new Point(x, y); final float[] fs = new float[] {x, y}; mMatrix.mapPoints(fs); @@ -92,7 +199,7 @@ public class CoordinateTransformsTest { }; } - public interface DevicePointAssertable { - void mapsToLogicalPoint(int x, int y); + public interface TransformPointAssertable { + void transformsTo(int x, int y); } } \ No newline at end of file From 28c25e219a401c1d5f7591fec28308d221f20762 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Thu, 31 May 2018 18:07:28 +0200 Subject: [PATCH 2/2] DO NOT MERGE 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). Do not merge, because we want to harmonize seamless rotation and the newly added forced seamless rotation in Q. 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. Bug: 79112140 Test: Enable display cutout overlay, rotate phone to all orientations, ensure that emulated display cutout never flashes or disappears. Test: atest CoordinateTransformsTest Change-Id: I90451c14dc28daa3f90a74c3117548fead25af3f --- .../android/systemui/ScreenDecorations.java | 57 ++++++++++++-- .../com/android/server/display/ColorFade.java | 3 +- .../server/policy/WindowManagerPolicy.java | 2 + .../com/android/server/wm/DisplayContent.java | 21 +++++ .../server/wm/ForcedSeamlessRotator.java | 77 +++++++++++++++++++ .../server/wm/WindowManagerService.java | 6 ++ .../com/android/server/wm/WindowState.java | 25 +++++- .../server/wm/WindowStateAnimator.java | 14 +++- .../com/android/server/wm/WindowToken.java | 6 -- .../wm/utils/CoordinateTransformsTest.java | 2 +- 10 files changed, 196 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 65826b9fbbc99..6a63b043331e0 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -52,6 +52,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; @@ -91,6 +92,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { private int mRotation; private DisplayCutoutView mCutoutTop; private DisplayCutoutView mCutoutBottom; + private boolean mPendingRotationChange; @Override public void start() { @@ -124,6 +126,21 @@ public class ScreenDecorations extends SystemUI implements Tunable { @Override public void onDisplayChanged(int displayId) { + if (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(); } }; @@ -138,12 +155,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); @@ -201,6 +218,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { @Override protected void onConfigurationChanged(Configuration newConfig) { + mPendingRotationChange = false; updateOrientation(); if (shouldDrawCutout() && mOverlay == null) { setupDecorations(); @@ -208,6 +226,9 @@ public class ScreenDecorations extends SystemUI implements Tunable { } protected void updateOrientation() { + if (mPendingRotationChange) { + return; + } int newRotation = RotationUtils.getExactRotation(mContext); if (newRotation != mRotation) { mRotation = newRotation; @@ -423,15 +444,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); } @@ -494,10 +517,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(); @@ -660,4 +683,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 a02ee225861db..e11b64287fe3f 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -157,6 +157,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 2857603e372cb..5386b1a91eb92 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1106,6 +1106,10 @@ class DisplayContent extends WindowContainer { + w.forceSeamlesslyRotateIfAllowed(oldRotation, rotation); + }, true /* traverseTopToBottom */); + if (rotateSeamlessly) { seamlesslyRotate(getPendingTransaction(), oldRotation, rotation); } @@ -3690,6 +3694,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. + */ + final boolean mForceSeamlesslyRotate; + ForcedSeamlessRotator mPendingForcedSeamlessRotate; private RemoteCallbackList mFocusCallbacks; @@ -669,6 +676,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); @@ -715,6 +730,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); @@ -4700,7 +4716,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()) { @@ -4855,7 +4874,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 989d6941e16fb..0b50802023eeb 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -685,8 +685,11 @@ class WindowStateAnimator { final int displayId = mWin.getDisplayId(); final ScreenRotationAnimation screenRotationAnimation = mAnimator.getScreenRotationAnimationLocked(displayId); - final boolean screenAnimation = - screenRotationAnimation != null && screenRotationAnimation.isAnimating(); + final boolean windowParticipatesInScreenRotationAnimation = + !mWin.mForceSeamlesslyRotate; + final boolean screenAnimation = screenRotationAnimation != null + && screenRotationAnimation.isAnimating() + && windowParticipatesInScreenRotationAnimation; if (screenAnimation) { // cache often used attributes locally @@ -798,6 +801,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) { 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() {