Implement seamless rotation mode.

Add a rotation mode which does not require freezing
the screen. For situations like Camera where only small
elements move on screen, this allows for seamless changes
of display orientation. This is achieved by transforming the
windows with their current buffer in the same transaction that
we rotate the display. We set things up so the windows are
frozen this way until they submit buffers in the new orientation.
There is a special case in the Camera window itself, and it's use
of NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY. In this case the buffer
contents are rotated by SurfaceFlinger and will never resize, for these
windows we just need to update the scaling matrix.

Bug: 28823590
Change-Id: I52dc6a86fcb3c08f736f0977ba3975a24fb8136c
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 7da849a..286e097 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -58,6 +58,7 @@
 
     private static native long nativeGetNextFrameNumber(long nativeObject);
     private static native int nativeSetScalingMode(long nativeObject, int scalingMode);
+    private static native void nativeSetBuffersTransform(long nativeObject, long transform);
 
     public static final Parcelable.Creator<Surface> CREATOR =
             new Parcelable.Creator<Surface>() {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 415e70c..f1abca8 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -51,7 +51,7 @@
 
     private static native void nativeSetLayer(long nativeObject, int zorder);
     private static native void nativeSetPosition(long nativeObject, float x, float y);
-    private static native void nativeSetPositionAppliesWithResize(long nativeObject);
+    private static native void nativeSetGeometryAppliesWithResize(long nativeObject);
     private static native void nativeSetSize(long nativeObject, int w, int h);
     private static native void nativeSetTransparentRegionHint(long nativeObject, Region region);
     private static native void nativeSetAlpha(long nativeObject, float alpha);
@@ -89,6 +89,8 @@
     private static native void nativeSetOverrideScalingMode(long nativeObject,
             int scalingMode);
     private static native IBinder nativeGetHandle(long nativeObject);
+    private static native boolean nativeGetTransformToDisplayInverse(long nativeObject);
+
     private static native Display.HdrCapabilities nativeGetHdrCapabilities(IBinder displayToken);
 
 
@@ -393,6 +395,10 @@
         return nativeGetHandle(mNativeObject);
     }
 
+    public boolean getTransformToDisplayInverse() {
+        return nativeGetTransformToDisplayInverse(mNativeObject);
+    }
+
     /** flag the transaction as an animation */
     public static void setAnimationTransaction() {
         nativeSetAnimationTransaction();
@@ -409,13 +415,15 @@
     }
 
     /**
-     * If the size changes in this transaction, position updates specified
+     * If the buffer size changes in this transaction, position and crop updates specified
      * in this transaction will not complete until a buffer of the new size
-     * arrives.
+     * arrives. As transform matrix and size are already frozen in this fashion,
+     * this enables totally freezing the surface until the resize has completed
+     * (at which point the geometry influencing aspects of this transaction will then occur)
      */
-    public void setPositionAppliesWithResize() {
+    public void setGeometryAppliesWithResize() {
         checkNotReleased();
-        nativeSetPositionAppliesWithResize(mNativeObject);
+        nativeSetGeometryAppliesWithResize(mNativeObject);
     }
 
     public void setSize(int w, int h) {
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index e6f5b83..a8afaf2 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1410,4 +1410,6 @@
      * Called when the configuration has changed, and it's safe to load new values from resources.
      */
     public void onConfigurationChanged();
+
+    public boolean shouldRotateSeamlessly(int oldRotation, int newRotation);
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index ff75677..fa1313b 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -248,10 +248,10 @@
     }
 }
 
-static void nativeSetPositionAppliesWithResize(JNIEnv* env, jclass clazz,
+static void nativeSetGeometryAppliesWithResize(JNIEnv* env, jclass clazz,
         jlong nativeObject) {
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    status_t err = ctrl->setPositionAppliesWithResize();
+    status_t err = ctrl->setGeometryAppliesWithResize();
     if (err < 0 && err != NO_INIT) {
         doThrowIAE(env);
     }
@@ -626,6 +626,16 @@
     return javaObjectForIBinder(env, ctrl->getHandle());
 }
 
+static jboolean nativeGetTransformToDisplayInverse(JNIEnv* env, jclass clazz, jlong nativeObject) {
+    bool out = false;
+    auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+    status_t status = ctrl->getTransformToDisplayInverse(&out);
+    if (status != NO_ERROR) {
+        return false;
+    }
+    return out;
+}
+
 static jobject nativeGetHdrCapabilities(JNIEnv* env, jclass clazz, jobject tokenObject) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
     if (token == NULL) return NULL;
@@ -667,8 +677,8 @@
             (void*)nativeSetLayer },
     {"nativeSetPosition", "(JFF)V",
             (void*)nativeSetPosition },
-    {"nativeSetPositionAppliesWithResize", "(J)V",
-            (void*)nativeSetPositionAppliesWithResize },
+    {"nativeSetGeometryAppliesWithResize", "(J)V",
+            (void*)nativeSetGeometryAppliesWithResize },
     {"nativeSetSize", "(JII)V",
             (void*)nativeSetSize },
     {"nativeSetTransparentRegionHint", "(JLandroid/graphics/Region;)V",
@@ -722,7 +732,9 @@
     {"nativeSetOverrideScalingMode", "(JI)V",
             (void*)nativeSetOverrideScalingMode },
     {"nativeGetHandle", "(J)Landroid/os/IBinder;",
-            (void*)nativeGetHandle }
+            (void*)nativeGetHandle },
+    {"nativeGetTransformToDisplayInverse", "(J)Z",
+            (void*)nativeGetTransformToDisplayInverse },
 };
 
 int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ea47303..7e805ea 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -7670,6 +7670,34 @@
     }
 
     @Override
+    public boolean shouldRotateSeamlessly(int oldRotation, int newRotation) {
+        // For the upside down rotation we don't rotate seamlessly as the navigation
+        // bar moves position.
+        // Note most apps (using orientation:sensor or user as opposed to fullSensor)
+        // will not enter the reverse portrait orientation, so actually the
+        // orientation won't change at all.
+        if (oldRotation == mUpsideDownRotation || newRotation == mUpsideDownRotation) {
+            return false;
+        }
+        int delta = newRotation - oldRotation;
+        if (delta < 0) delta += 4;
+        // Likewise we don't rotate seamlessly for 180 degree rotations
+        // in this case the surfaces never resize, and our logic to 
+        // revert the transformations on size change will fail. We could
+        // fix this in the future with the "tagged" frames idea.
+        if (delta == Surface.ROTATION_180) {
+            return false;
+        }
+
+        if (mTopFullscreenOpaqueWindowState != null && mTopIsFullscreen &&
+                mTopFullscreenOpaqueWindowState.getAttrs().rotationAnimation ==
+                ROTATION_ANIMATION_JUMPCUT) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
     public void dump(String prefix, PrintWriter pw, String[] args) {
         pw.print(prefix); pw.print("mSafeMode="); pw.print(mSafeMode);
                 pw.print(" mSystemReady="); pw.print(mSystemReady);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3687512..b8b7848 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -41,8 +41,10 @@
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.graphics.PixelFormat;
+import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Region;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
@@ -291,6 +293,9 @@
     /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
     static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
 
+    /** Amount of time (in milliseconds) to delay before declaring a seamless rotation timeout. */
+    static final int SEAMLESS_ROTATION_TIMEOUT_DURATION = 2000;
+
     /** Amount of time (in milliseconds) to delay before declaring a window replacement timeout. */
     static final int WINDOW_REPLACEMENT_TIMEOUT_DURATION = 2000;
 
@@ -516,6 +521,9 @@
     final Rect mTmpRect = new Rect();
     final Rect mTmpRect2 = new Rect();
     final Rect mTmpRect3 = new Rect();
+    final RectF mTmpRectF = new RectF();
+
+    final Matrix mTmpTransform = new Matrix();
 
     boolean mDisplayReady;
     boolean mSafeMode;
@@ -6567,6 +6575,7 @@
         Binder.restoreCallingIdentity(origId);
     }
 
+
     // TODO(multidisplay): Rotate any display?
     /**
      * Updates the current rotation.
@@ -6626,6 +6635,8 @@
                 + ", forceApp=" + mForcedAppOrientation);
         }
 
+        int oldRotation = mRotation;
+
         mRotation = rotation;
         mAltOrientation = altOrientation;
         mPolicy.setRotationLw(mRotation);
@@ -6642,10 +6653,32 @@
         } else {
             mPolicy.selectRotationAnimationLw(anim);
         }
-        startFreezingDisplayLocked(inTransaction, anim[0], anim[1]);
-        // startFreezingDisplayLocked can reset the ScreenRotationAnimation.
-        screenRotationAnimation =
+        boolean rotateSeamlessly = mPolicy.shouldRotateSeamlessly(oldRotation, mRotation);
+        final WindowList windows = displayContent.getWindowList();
+        // We can't rotate seamlessly while an existing seamless rotation is still
+        // waiting on windows to finish drawing.
+        if (rotateSeamlessly) {
+            for (int i = windows.size() - 1; i >= 0; i--) {
+                WindowState w = windows.get(i);
+                if (w.mSeamlesslyRotated) {
+                    rotateSeamlessly = false;
+                    break;
+                }
+            }
+        }
+
+        if (!rotateSeamlessly) {
+            startFreezingDisplayLocked(inTransaction, anim[0], anim[1]);
+            // startFreezingDisplayLocked can reset the ScreenRotationAnimation.
+            screenRotationAnimation =
                 mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
+        } else {
+            // The screen rotation animation uses a screenshot to freeze the screen
+            // while windows resize underneath.
+            // When we are rotating seamlessly, we allow the elements to transition
+            // to their rotated state independently and without a freeze required.
+            screenRotationAnimation = null;
+        }
 
         // We need to update our screen size information to match the new rotation. If the rotation
         // has actually changed then this method will return true and, according to the comment at
@@ -6674,6 +6707,13 @@
                 }
             }
 
+            if (rotateSeamlessly) {
+                for (int i = windows.size() - 1; i >= 0; i--) {
+                    WindowState w = windows.get(i);
+                    w.mWinAnimator.seamlesslyRotateWindow(oldRotation, mRotation);
+                }
+            }
+
             mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
         } finally {
             if (!inTransaction) {
@@ -6684,19 +6724,22 @@
             }
         }
 
-        final WindowList windows = displayContent.getWindowList();
         for (int i = windows.size() - 1; i >= 0; i--) {
             WindowState w = windows.get(i);
             // Discard surface after orientation change, these can't be reused.
             if (w.mAppToken != null) {
                 w.mAppToken.destroySavedSurfaces();
             }
-            if (w.mHasSurface) {
+            if (w.mHasSurface && !rotateSeamlessly) {
                 if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Set mOrientationChanging of " + w);
                 w.mOrientationChanging = true;
                 mWindowPlacerLocked.mOrientationChangeComplete = false;
+                w.mLastFreezeDuration = 0;
             }
-            w.mLastFreezeDuration = 0;
+        }
+        if (rotateSeamlessly) {
+            mH.removeMessages(H.SEAMLESS_ROTATION_TIMEOUT);
+            mH.sendEmptyMessageDelayed(H.SEAMLESS_ROTATION_TIMEOUT, SEAMLESS_ROTATION_TIMEOUT_DURATION);
         }
 
         for (int i=mRotationWatchers.size()-1; i>=0; i--) {
@@ -6706,7 +6749,7 @@
             }
         }
 
-        //TODO (multidisplay): Magnification is supported only for the default display.
+        // TODO (multidisplay): Magnification is supported only for the default display.
         // Announce rotation only if we will not animate as we already have the
         // windows in final state. Otherwise, we make this call at the rotation end.
         if (screenRotationAnimation == null && mAccessibilityController != null
@@ -7994,8 +8037,8 @@
         public static final int NOTIFY_STARTING_WINDOW_DRAWN = 50;
         public static final int UPDATE_ANIMATION_SCALE = 51;
         public static final int WINDOW_REMOVE_TIMEOUT = 52;
-
         public static final int NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED = 53;
+        public static final int SEAMLESS_ROTATION_TIMEOUT = 54;
 
         /**
          * Used to denote that an integer field in a message will not be used.
@@ -8635,6 +8678,25 @@
                     mAmInternal.notifyDockedStackMinimizedChanged(msg.arg1 == 1);
                 }
                 break;
+                case SEAMLESS_ROTATION_TIMEOUT: {
+                    // Rotation only supported on primary display.
+                    // TODO(multi-display)
+                    final DisplayContent displayContent = getDefaultDisplayContentLocked();
+                    final WindowList windows = displayContent.getWindowList();
+                    boolean layoutNeeded = false;
+                    for (int i = windows.size() - 1; i >= 0; i--) {
+                        WindowState w = windows.get(i);
+                        if (w.mSeamlesslyRotated) {
+                            layoutNeeded = true;
+                            w.setDisplayLayoutNeeded();
+                        }
+                        w.mSeamlesslyRotated = false;
+                    }
+                    if (layoutNeeded) {
+                        mWindowPlacerLocked.performSurfacePlacement();
+                    }
+                }
+                break;
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG_WM, "handleMessage: exit");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 08bfa2d..99fec7b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -494,6 +494,15 @@
     /** @see #isResizedWhileNotDragResizingReported(). */
     private boolean mResizedWhileNotDragResizingReported;
 
+    /**
+     * During seamless rotation we have two phases, first the old window contents
+     * are rotated to look as if they didn't move in the new coordinate system. Then we
+     * have to freeze updates to this layer (to preserve the transformation) until
+     * the resize actually occurs. This is true from when the transformation is set
+     * and false until the transaction to resize is sent.
+     */
+    boolean mSeamlesslyRotated = false;
+
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
            int viewVisibility, final DisplayContent displayContent) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 36d9697..62c70b3 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -51,12 +51,14 @@
 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.RemoteException;
 import android.util.Slog;
 import android.view.DisplayInfo;
 import android.view.MagnificationSpec;
+import android.view.Surface;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -1400,6 +1402,9 @@
         mExtraHScale = (float) 1.0;
         mExtraVScale = (float) 1.0;
 
+        boolean wasForceScaled = mForceScaleUntilResize;
+        boolean wasSeamlesslyRotated = w.mSeamlesslyRotated;
+
         // Once relayout has been called at least once, we need to make sure
         // we only resize the client surface during calls to relayout. For
         // clients which use indeterminate measure specs (MATCH_PARENT),
@@ -1412,9 +1417,6 @@
         // aren't observing known issues here outside of PiP resizing. (Typically
         // the other windows that use -1 are PopupWindows which aren't likely
         // to be rendering while we resize).
-
-        boolean wasForceScaled = mForceScaleUntilResize;
-
         if (!w.inPinnedWorkspace() || (!w.mRelayoutCalled || w.mInRelayout)) {
             mSurfaceResized = mSurfaceController.setSizeInTransaction(
                     mTmpSize.width(), mTmpSize.height(), recoveringMemory);
@@ -1422,6 +1424,10 @@
             mSurfaceResized = false;
         }
         mForceScaleUntilResize = mForceScaleUntilResize && !mSurfaceResized;
+        // If we are undergoing seamless rotation, the surface has already
+        // been set up to persist at it's old location. We need to freeze
+        // updates until a resize occurs.
+        w.mSeamlesslyRotated = w.mSeamlesslyRotated && !mSurfaceResized;
 
         calculateSurfaceWindowCrop(mTmpClipRect, mTmpFinalClipRect);
 
@@ -1471,17 +1477,20 @@
             // will be seamless.
             mForceScaleUntilResize = true;
         } else {
-            mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top,
-                    recoveringMemory);
+            if (!w.mSeamlesslyRotated) {
+                mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top,
+                        recoveringMemory);
+            }
         }
 
         // If we are ending the scaling mode. We switch to SCALING_MODE_FREEZE
-        // to prevent further updates until buffer latch. Normally position
-        // would continue to apply immediately. But we need a different position
-        // before and after resize (since we have scaled the shadows, as discussed
-        // above).
-        if (wasForceScaled && !mForceScaleUntilResize) {
-            mSurfaceController.setPositionAppliesWithResizeInTransaction(true);
+        // to prevent further updates until buffer latch.
+        // When ending both force scaling, and seamless rotation, we need to freeze
+        // the Surface geometry until a buffer comes in at the new size (normally position and crop
+        // are unfrozen). setGeometryAppliesWithResizeInTransaction accomplishes this for us.
+        if ((wasForceScaled && !mForceScaleUntilResize) ||
+                (wasSeamlesslyRotated && !w.mSeamlesslyRotated)) {
+            mSurfaceController.setGeometryAppliesWithResizeInTransaction(true);
             mSurfaceController.forceScaleableInTransaction(false);
         }
 
@@ -1493,12 +1502,13 @@
                     -w.mAttrs.surfaceInsets.right, -w.mAttrs.surfaceInsets.bottom);
         }
 
-        updateSurfaceWindowCrop(clipRect, mTmpFinalClipRect, recoveringMemory);
-
-        mSurfaceController.setMatrixInTransaction(mDsDx * w.mHScale * mExtraHScale,
-                mDtDx * w.mVScale * mExtraVScale,
-                mDsDy * w.mHScale * mExtraHScale,
-                mDtDy * w.mVScale * mExtraVScale, recoveringMemory);
+        if (!w.mSeamlesslyRotated) {
+            updateSurfaceWindowCrop(clipRect, mTmpFinalClipRect, recoveringMemory);
+            mSurfaceController.setMatrixInTransaction(mDsDx * w.mHScale * mExtraHScale,
+                    mDtDx * w.mVScale * mExtraVScale,
+                    mDsDy * w.mHScale * mExtraHScale,
+                    mDtDy * w.mVScale * mExtraVScale, recoveringMemory);
+        }
 
         if (mSurfaceResized) {
             mReportSurfaceResized = true;
@@ -2084,4 +2094,98 @@
     void endDelayingAnimationStart() {
         mAnimationStartDelayed = false;
     }
+
+    void seamlesslyRotateWindow(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;
+        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().getLogicalDisplayRect(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);
+        switch (deltaRotation) {
+        case Surface.ROTATION_0:
+            transform.reset();
+            break;
+        case Surface.ROTATION_270:
+            transform.setRotate(270, 0, 0);
+            transform.postTranslate(0, displayHeight);
+            transform.postTranslate(y, 0);
+            break;
+        case Surface.ROTATION_180:
+            transform.reset();
+            break;
+        case Surface.ROTATION_90:
+            transform.setRotate(90, 0, 0);
+            transform.postTranslate(displayWidth, 0);
+            transform.postTranslate(-y, x);
+            break;
+        }
+
+        // We have two cases:
+        //  1. Windows with NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY:
+        //     These windows never change buffer size when rotating. Rather the window manager
+        //     just updates the scaling factors to fit in the new coordinate system,
+        //     and SurfaceFlinger takes care of updating the buffer contents. So in this case
+        //     we just need we just need to update the scaling factors and things are seamless
+        //     already.
+        //  2. Other windows:
+        //     In this case, we 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.
+        if (w.isChildWindow() && mSurfaceController.getTransformToDisplayInverse()) {
+            frameRect.set(x, y, x+width, y+height);
+            transform.mapRect(frameRect);
+
+            w.mAttrs.x = (int) frameRect.left - w.mAttachedWindow.mFrame.left;
+            w.mAttrs.y = (int) frameRect.top - w.mAttachedWindow.mFrame.top;
+            w.mAttrs.width = (int) Math.ceil(frameRect.width());
+            w.mAttrs.height = (int) Math.ceil(frameRect.height());
+
+            w.setWindowScale(w.mRequestedWidth, w.mRequestedHeight);
+
+            w.applyGravityAndUpdateFrame(w.mContainingFrame, w.mDisplayFrame);
+            computeShownFrameLocked();
+            setSurfaceBoundariesLocked(false);
+
+            // The stack bounds will not yet be rotated at this point so setSurfaceBoundaries locked
+            // will crop us incorrectly. Overwrite the crop, exposing the full surface. By the next
+            // transaction this will be corrected.
+            cropRect.set(0, 0, w.mRequestedWidth, w.mRequestedWidth + w.mRequestedHeight);
+            mSurfaceController.setCropInTransaction(cropRect, false);
+        } else {
+            w.mSeamlesslyRotated = true;
+            transform.getValues(mService.mTmpFloats);
+
+            float DsDx = mService.mTmpFloats[Matrix.MSCALE_X];
+            float DtDx = mService.mTmpFloats[Matrix.MSKEW_Y];
+            float DsDy = mService.mTmpFloats[Matrix.MSKEW_X];
+            float DtDy = mService.mTmpFloats[Matrix.MSCALE_Y];
+            float nx = mService.mTmpFloats[Matrix.MTRANS_X];
+            float ny = mService.mTmpFloats[Matrix.MTRANS_Y];
+            mSurfaceController.setPositionInTransaction(nx, ny, false);
+            mSurfaceController.setMatrixInTransaction(DsDx * w.mHScale,
+                    DtDx * w.mVScale,
+                    DsDy * w.mHScale,
+                    DtDy * w.mVScale, false);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index fd0bb99..3121415 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -259,8 +259,8 @@
         }
     }
 
-    void setPositionAppliesWithResizeInTransaction(boolean recoveringMemory) {
-        mSurfaceControl.setPositionAppliesWithResize();
+    void setGeometryAppliesWithResizeInTransaction(boolean recoveringMemory) {
+        mSurfaceControl.setGeometryAppliesWithResize();
     }
 
     void setMatrixInTransaction(float dsdx, float dtdx, float dsdy, float dtdy,
@@ -457,6 +457,10 @@
         return mSurfaceControl.getHandle();
     }
 
+    boolean getTransformToDisplayInverse() {
+        return mSurfaceControl.getTransformToDisplayInverse();
+    }
+
     void getSurface(Surface outSurface) {
         outSurface.copyFrom(mSurfaceControl);
     }
@@ -581,10 +585,10 @@
         }
 
         @Override
-        public void setPositionAppliesWithResize() {
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setPositionAppliesWithResize(): OLD: "
-                    + this + ". Called by" + Debug.getCallers(9));
-            super.setPositionAppliesWithResize();
+        public void setGeometryAppliesWithResize() {
+            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setGeometryAppliesWithResize(): OLD: "
+                    + this + ". Called by" + Debug.getCallers(3));
+            super.setGeometryAppliesWithResize();
         }
 
         @Override