Merge changes I90451c14,I9fb27a0a into pi-dev
am: b91dd1d944

Change-Id: I58459846d6cb79d616b4bd9c83450dc911e5115c
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 65826b9..6a63b04 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.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 @@
     private int mRotation;
     private DisplayCutoutView mCutoutTop;
     private DisplayCutoutView mCutoutBottom;
+    private boolean mPendingRotationChange;
 
     @Override
     public void start() {
@@ -124,6 +126,21 @@
 
             @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 @@
         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 @@
 
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
+        mPendingRotationChange = false;
         updateOrientation();
         if (shouldDrawCutout() && mOverlay == null) {
             setupDecorations();
@@ -208,6 +226,9 @@
     }
 
     protected void updateOrientation() {
+        if (mPendingRotationChange) {
+            return;
+        }
         int newRotation = RotationUtils.getExactRotation(mContext);
         if (newRotation != mRotation) {
             mRotation = newRotation;
@@ -423,15 +444,17 @@
         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 @@
         }
 
         private void update() {
-            mStart = isStart();
-            if (!isAttachedToWindow()) {
+            if (!isAttachedToWindow() || mDecorations.mPendingRotationChange) {
                 return;
             }
+            mStart = isStart();
             requestLayout();
             getDisplay().getDisplayInfo(mInfo);
             mBounds.setEmpty();
@@ -660,4 +683,28 @@
         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 4f53ed4..33525fd 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 libcore.io.Streams;
 
 import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy;
 
 /**
  * <p>
@@ -63,7 +64,7 @@
 
     // 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 a02ee22..e11b642 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 @@
     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 cd8fdbf..cfbcd17 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 @@
             }
         }
 
+        forAllWindows(w -> {
+            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());
@@ -3701,6 +3702,19 @@
         }
 
         @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 */);
         }
@@ -3716,6 +3730,10 @@
                     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);
 
diff --git a/services/core/java/com/android/server/wm/ForcedSeamlessRotator.java b/services/core/java/com/android/server/wm/ForcedSeamlessRotator.java
new file mode 100644
index 0000000..3218e83
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ForcedSeamlessRotator.java
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+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());
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 19c5a3d..8fe7063 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -736,6 +736,20 @@
     }
 
     /**
+     * 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 7a2c28b..ef74586 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 @@
             }
 
             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 bee70a0..a7f432d 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.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 @@
     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<IWindowFocusObserver> mFocusCallbacks;
 
@@ -667,6 +676,14 @@
 
     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 @@
         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 @@
                 && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
                 && !isDragResizing() && !adjustedForMinimizedDockOrIme
                 && getWindowConfiguration().hasMovementAnimations()
-                && !mWinAnimator.mLastHidden) {
+                && !mWinAnimator.mLastHidden
+                && !mSeamlesslyRotated) {
             startMoveAnimation(left, top);
         }
 
@@ -4697,7 +4716,10 @@
 
         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 @@
         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 3eef125..0b50802 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.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 @@
         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 @@
             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 @@
         }
     }
 
-    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 b97460a..e411c0a 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 @@
         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 09d7b5d..a2f37a5 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_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 @@
                 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 85e846d..9f113ad 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 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.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 @@
         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 40a10e0..f82b012 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_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 @@
     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 @@
     @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 interface DevicePointAssertable {
-        void mapsToLogicalPoint(int x, int y);
+    public interface TransformPointAssertable {
+        void transformsTo(int x, int y);
     }
 }
\ No newline at end of file