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 cd8fdbfbc6476..cfbcd176eaf49 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1106,11 +1106,12 @@ class DisplayContent extends WindowContainer { + w.forceSeamlesslyRotateIfAllowed(oldRotation, rotation); + }, true /* traverseTopToBottom */); + if (rotateSeamlessly) { - forAllWindows(w -> { - w.mWinAnimator.seamlesslyRotateWindow(getPendingTransaction(), - oldRotation, rotation); - }, true /* traverseTopToBottom */); + seamlesslyRotate(getPendingTransaction(), oldRotation, rotation); } mService.mDisplayManagerInternal.performTraversal(getPendingTransaction()); @@ -3700,6 +3701,19 @@ class DisplayContent extends 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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7a2c28bd5c826..ef745869b2775 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1883,6 +1883,12 @@ public class WindowManagerService extends IWindowManager.Stub } win.setFrameNumber(frameNumber); + + if (win.mPendingForcedSeamlessRotate != null && !mWaitingForConfig) { + win.mPendingForcedSeamlessRotate.finish(win.mToken, win); + win.mPendingForcedSeamlessRotate = null; + } + int attrChanges = 0; int flagChanges = 0; if (attrs != null) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index bee70a0119459..a7f432db84564 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; @@ -278,6 +280,13 @@ class WindowState 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; @@ -667,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); @@ -713,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); @@ -1811,7 +1829,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); } @@ -4697,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()) { @@ -4850,6 +4872,31 @@ class WindowState extends WindowContainer implements WindowManagerP mFrameNumber = frameNumber; } + @Override + void seamlesslyRotate(Transaction t, int oldRotation, int newRotation) { + // 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; + + 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..0b50802023eeb 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; @@ -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) { @@ -1492,40 +1502,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/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/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..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 @@ -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