WM: Consolidate seamless rotation logic

Replaces existing seamless rotation logic with the new forced seamless
rotator.

Bug: b/110763772, b/111504081

Test: go/wm-smoke
Test: atest FrameworksServicesTests:com.android.server.wm.WindowStateTests
Test: Build winscope with new proto changes, open new and old traces and verify data can be read correctly

Change-Id: I818d4ce66d8ff3ed268b5d43c6012398672bbfee
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d15271c..e46ad2c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -7862,15 +7862,6 @@
         if (!displayRotation.getDisplayPolicy().navigationBarCanMove()) {
             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;
-        }
 
         final WindowState w = mTopFullscreenOpaqueWindowState;
         if (w != mFocusedWindow) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 49638a9..a2dd679 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1199,14 +1199,10 @@
         }
 
         forAllWindows(w -> {
-            w.forceSeamlesslyRotateIfAllowed(oldRotation, rotation);
+            w.seamlesslyRotateIfAllowed(getPendingTransaction(), oldRotation, rotation,
+                    rotateSeamlessly);
         }, true /* traverseTopToBottom */);
 
-        // TODO(b/111504081): Consolidate seamless rotation logic.
-        if (rotateSeamlessly) {
-            seamlesslyRotate(getPendingTransaction(), oldRotation, rotation);
-        }
-
         mService.mDisplayManagerInternal.performTraversal(getPendingTransaction());
         scheduleAnimation();
 
diff --git a/services/core/java/com/android/server/wm/ForcedSeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java
similarity index 84%
rename from services/core/java/com/android/server/wm/ForcedSeamlessRotator.java
rename to services/core/java/com/android/server/wm/SeamlessRotator.java
index f5e6e72..6f597277 100644
--- a/services/core/java/com/android/server/wm/ForcedSeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/SeamlessRotator.java
@@ -16,12 +16,14 @@
 
 package com.android.server.wm;
 
+import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
 import android.graphics.Matrix;
 import android.view.DisplayInfo;
 import android.view.Surface.Rotation;
+import android.view.SurfaceControl.Transaction;
 
 import com.android.server.wm.utils.CoordinateTransforms;
 
@@ -29,22 +31,21 @@
 import java.io.StringWriter;
 
 /**
- * Helper class for forced seamless rotation.
+ * Helper class for 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.
- * TODO(b/111504081): Consolidate seamless rotation logic.
  */
-public class ForcedSeamlessRotator {
+public class SeamlessRotator {
 
     private final Matrix mTransform = new Matrix();
     private final float[] mFloat9 = new float[9];
     private final int mOldRotation;
     private final int mNewRotation;
 
-    public ForcedSeamlessRotator(int oldRotation, int newRotation, DisplayInfo info) {
+    public SeamlessRotator(int oldRotation, int newRotation, DisplayInfo info) {
         mOldRotation = oldRotation;
         mNewRotation = newRotation;
 
@@ -62,8 +63,8 @@
      * 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);
+    public void unrotate(Transaction transaction, WindowToken token) {
+        transaction.setMatrix(token.getSurfaceControl(), mTransform, mFloat9);
     }
 
     /**
@@ -94,6 +95,10 @@
             win.getPendingTransaction().deferTransactionUntil(win.mSurfaceControl,
                     win.mWinAnimator.mSurfaceController.mSurfaceControl.getHandle(),
                     win.getFrameNumber());
+            win.getPendingTransaction().deferTransactionUntil(
+                    win.mWinAnimator.mSurfaceController.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 fee0fcb..8e704a8 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -122,7 +122,7 @@
     private int mTreeWeight = 1;
 
     /**
-     * Indicates whether we are animating and have committed the transaction to reparent our 
+     * Indicates whether we are animating and have committed the transaction to reparent our
      * surface to the animation leash
      */
     private boolean mCommittedReparentToAnimationLeash;
@@ -749,20 +749,6 @@
     }
 
     /**
-     * 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 8a57143..fa17001 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1896,11 +1896,8 @@
 
             win.setFrameNumber(frameNumber);
 
-            // TODO(b/111504081): Consolidate seamless rotation logic.
-            if (win.mPendingForcedSeamlessRotate != null && !mWaitingForConfig) {
-                win.mPendingForcedSeamlessRotate.finish(win.mToken, win);
-                win.mFinishForcedSeamlessRotateFrameNumber = win.getFrameNumber();
-                win.mPendingForcedSeamlessRotate = null;
+            if (!mWaitingForConfig) {
+                win.finishSeamlessRotation();
             }
 
             int attrChanges = 0;
@@ -7108,7 +7105,7 @@
     }
 
     void markForSeamlessRotation(WindowState w, boolean seamlesslyRotated) {
-        if (seamlesslyRotated == w.mSeamlesslyRotated) {
+        if (seamlesslyRotated == w.mSeamlesslyRotated || w.mForceSeamlesslyRotate) {
             return;
         }
         w.mSeamlesslyRotated = seamlesslyRotated;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 58fb7a0..97313f2 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -71,12 +71,20 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
+
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
+import static com.android.server.wm.AnimationSpecProto.MOVE;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
+import static com.android.server.wm.IdentifierProto.HASH_CODE;
+import static com.android.server.wm.IdentifierProto.TITLE;
+import static com.android.server.wm.IdentifierProto.USER_ID;
+import static com.android.server.wm.MoveAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.MoveAnimationSpecProto.FROM;
+import static com.android.server.wm.MoveAnimationSpecProto.TO;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
@@ -108,13 +116,6 @@
 import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
 import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
 import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
-import static com.android.server.wm.IdentifierProto.HASH_CODE;
-import static com.android.server.wm.IdentifierProto.TITLE;
-import static com.android.server.wm.IdentifierProto.USER_ID;
-import static com.android.server.wm.AnimationSpecProto.MOVE;
-import static com.android.server.wm.MoveAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.MoveAnimationSpecProto.FROM;
-import static com.android.server.wm.MoveAnimationSpecProto.TO;
 import static com.android.server.wm.WindowStateProto.ANIMATING_EXIT;
 import static com.android.server.wm.WindowStateProto.ANIMATOR;
 import static com.android.server.wm.WindowStateProto.ATTRIBUTES;
@@ -122,7 +123,8 @@
 import static com.android.server.wm.WindowStateProto.CONTENT_INSETS;
 import static com.android.server.wm.WindowStateProto.DESTROYING;
 import static com.android.server.wm.WindowStateProto.DISPLAY_ID;
-import static com.android.server.wm.WindowStateProto.FINISHED_FORCED_SEAMLESS_ROTATION_FRAME;
+import static com.android.server.wm.WindowStateProto.FINISHED_SEAMLESS_ROTATION_FRAME;
+import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION;
 import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS;
 import static com.android.server.wm.WindowStateProto.HAS_SURFACE;
 import static com.android.server.wm.WindowStateProto.IDENTIFIER;
@@ -131,7 +133,7 @@
 import static com.android.server.wm.WindowStateProto.IS_VISIBLE;
 import static com.android.server.wm.WindowStateProto.OUTSETS;
 import static com.android.server.wm.WindowStateProto.OVERSCAN_INSETS;
-import static com.android.server.wm.WindowStateProto.PENDING_FORCED_SEAMLESS_ROTATION;
+import static com.android.server.wm.WindowStateProto.PENDING_SEAMLESS_ROTATION;
 import static com.android.server.wm.WindowStateProto.REMOVED;
 import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT;
 import static com.android.server.wm.WindowStateProto.REQUESTED_HEIGHT;
@@ -145,8 +147,6 @@
 import static com.android.server.wm.WindowStateProto.VISIBLE_INSETS;
 import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
-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;
@@ -279,11 +279,10 @@
      * Special mode that is intended only for the rounded corner overlay: during rotation
      * transition, we un-rotate the window token such that the window appears as it did before the
      * rotation.
-     * TODO(b/111504081): Consolidate seamless rotation logic.
      */
     final boolean mForceSeamlesslyRotate;
-    ForcedSeamlessRotator mPendingForcedSeamlessRotate;
-    long mFinishForcedSeamlessRotateFrameNumber;
+    SeamlessRotator mPendingSeamlessRotate;
+    long mFinishSeamlessRotateFrameNumber;
 
     private RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
 
@@ -632,15 +631,30 @@
 
     private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
 
-    void forceSeamlesslyRotateIfAllowed(int oldRotation, int rotation) {
-        if (mForceSeamlesslyRotate) {
-            if (mPendingForcedSeamlessRotate != null) {
-                oldRotation = mPendingForcedSeamlessRotate.getOldRotation();
-            }
+    void seamlesslyRotateIfAllowed(Transaction transaction, int oldRotation, int rotation,
+            boolean requested) {
+        // Invisible windows and the wallpaper do not participate in the seamless rotation animation
+        if (!isVisibleNow() || mIsWallpaper) {
+            return;
+        }
 
-            mPendingForcedSeamlessRotate = new ForcedSeamlessRotator(
-                    oldRotation, rotation, getDisplayInfo());
-            mPendingForcedSeamlessRotate.unrotate(this.mToken);
+        if (mPendingSeamlessRotate != null) {
+            oldRotation = mPendingSeamlessRotate.getOldRotation();
+        }
+
+        if (mForceSeamlesslyRotate || requested) {
+            mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo());
+            mPendingSeamlessRotate.unrotate(transaction, this.mToken);
+            mService.markForSeamlessRotation(this, true);
+        }
+    }
+
+    void finishSeamlessRotation() {
+        if (mPendingSeamlessRotate != null) {
+            mPendingSeamlessRotate.finish(this.mToken, this);
+            mFinishSeamlessRotateFrameNumber = getFrameNumber();
+            mPendingSeamlessRotate = null;
+            mService.markForSeamlessRotation(this, false);
         }
     }
 
@@ -3301,11 +3315,9 @@
         proto.write(REMOVED, mRemoved);
         proto.write(IS_ON_SCREEN, isOnScreen());
         proto.write(IS_VISIBLE, isVisible());
-        if (mForceSeamlesslyRotate) {
-            proto.write(PENDING_FORCED_SEAMLESS_ROTATION, mPendingForcedSeamlessRotate != null);
-            proto.write(FINISHED_FORCED_SEAMLESS_ROTATION_FRAME,
-                    mFinishForcedSeamlessRotateFrameNumber);
-        }
+        proto.write(PENDING_SEAMLESS_ROTATION, mPendingSeamlessRotate != null);
+        proto.write(FINISHED_SEAMLESS_ROTATION_FRAME, mFinishSeamlessRotateFrameNumber);
+        proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
         proto.end(token);
     }
 
@@ -3461,16 +3473,16 @@
             pw.print(prefix); pw.print("mLastFreezeDuration=");
                     TimeUtils.formatDuration(mLastFreezeDuration, pw); pw.println();
         }
-        if (mForceSeamlesslyRotate) {
-            pw.print(prefix); pw.print("forceSeamlesslyRotate: pending=");
-            if (mPendingForcedSeamlessRotate != null) {
-                mPendingForcedSeamlessRotate.dump(pw);
-            } else {
-                pw.print("null");
-            }
-            pw.print(" finishedFrameNumber="); pw.print(mFinishForcedSeamlessRotateFrameNumber);
-            pw.println();
+        pw.print(prefix); pw.print("mForceSeamlesslyRotate="); pw.print(mForceSeamlesslyRotate);
+        pw.print(" seamlesslyRotate: pending=");
+        if (mPendingSeamlessRotate != null) {
+            mPendingSeamlessRotate.dump(pw);
+        } else {
+            pw.print("null");
         }
+        pw.print(" finishedFrameNumber="); pw.print(mFinishSeamlessRotateFrameNumber);
+        pw.println();
+
         if (mHScale != 1 || mVScale != 1) {
             pw.print(prefix); pw.print("mHScale="); pw.print(mHScale);
                     pw.print(" mVScale="); pw.println(mVScale);
@@ -4716,8 +4728,8 @@
 
         // 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)) {
+        if (!mSurfaceAnimator.hasLeash() && mPendingSeamlessRotate == null
+                && !mLastSurfacePosition.equals(mSurfacePosition)) {
             t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
             mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
             if (surfaceInsetsChanging() && mWinAnimator.hasSurface()) {
@@ -4870,31 +4882,6 @@
         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, mWindowFrames.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);
-    }
-
     public void getMaxVisibleBounds(Rect out) {
         if (out.isEmpty()) {
             out.set(mWindowFrames.mVisibleFrame);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 7966d5b..b158ae2 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -686,7 +686,6 @@
         final int displayId = mWin.getDisplayId();
         final ScreenRotationAnimation screenRotationAnimation =
                 mAnimator.getScreenRotationAnimationLocked(displayId);
-        // TODO(b/111504081): Consolidate seamless rotation logic.
         final boolean windowParticipatesInScreenRotationAnimation =
                 !mWin.mForceSeamlesslyRotate;
         final boolean screenAnimation = screenRotationAnimation != null
@@ -878,7 +877,6 @@
         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
@@ -898,7 +896,6 @@
         // 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.
-        mService.markForSeamlessRotation(w, w.mSeamlesslyRotated && !mSurfaceResized);
 
         Rect clipRect = null;
         if (calculateCrop(mTmpClipRect)) {
@@ -1069,15 +1066,15 @@
 
         // If we are ending the scaling mode. We switch to SCALING_MODE_FREEZE
         // 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)) {
+        // We also 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) {
             mSurfaceController.setGeometryAppliesWithResizeInTransaction(true);
             mSurfaceController.forceScaleableInTransaction(false);
         }
 
+
         if (!w.mSeamlesslyRotated) {
             applyCrop(clipRect, recoveringMemory);
             mSurfaceController.setMatrixInTransaction(mDsDx * w.mHScale * mExtraHScale,
@@ -1509,29 +1506,6 @@
         }
     }
 
-    // TODO(b/111504081): Consolidate seamless rotation logic.
-    @Deprecated
-    void seamlesslyRotate(SurfaceControl.Transaction t, int oldRotation, int newRotation) {
-        final WindowState w = mWin;
-
-        // 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;
-        transformToRotation(oldRotation, newRotation, w.getFrameLw().width(),
-                w.getFrameLw().height(), transform);
-        transform.getValues(mService.mTmpFloats);
-
-        float DsDx = mService.mTmpFloats[Matrix.MSCALE_X];
-        float DtDx = mService.mTmpFloats[Matrix.MSKEW_Y];
-        float DtDy = mService.mTmpFloats[Matrix.MSKEW_X];
-        float DsDy = mService.mTmpFloats[Matrix.MSCALE_Y];
-        float nx = mService.mTmpFloats[Matrix.MTRANS_X];
-        float ny = mService.mTmpFloats[Matrix.MTRANS_Y];
-        mSurfaceController.setPosition(t, nx, ny, false);
-        mSurfaceController.setMatrix(t, DsDx * w.mHScale, DtDx * w.mVScale, DtDy
-                * w.mHScale, DsDy * w.mVScale, false);
-    }
-
     /** The force-scaled state for a given window can persist past
      * the state for it's stack as the windows complete resizing
      * independently of one another.