diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 3167437720b9c..e94e06168cacc 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1843,6 +1843,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } + /** Returns {@code true} if the decided new rotation has not applied to configuration yet. */ + private boolean isRotationChanging() { + return mDisplayRotation.getRotation() != getWindowConfiguration().getRotation(); + } + + private void startFadeRotationAnimationIfNeeded() { + if (isRotationChanging()) { + startFadeRotationAnimation(false /* shouldDebounce */); + } + } + /** * Starts the hide animation for the windows which will be rotated seamlessly. * @@ -3202,11 +3213,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Hide the windows which are not significant in rotation animation. So that the windows // don't need to block the unfreeze time. - if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot() - // Do not fade for freezing without rotation change. - && mDisplayRotation.getRotation() != getWindowConfiguration().getRotation() - && mFadeRotationAnimationController == null) { - startFadeRotationAnimation(false /* shouldDebounce */); + if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) { + startFadeRotationAnimationIfNeeded(); } } @@ -3227,6 +3235,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } if (!controller.isCollecting(this)) { controller.collect(this); + startFadeRotationAnimationIfNeeded(); } return; } @@ -3234,7 +3243,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp this, this, null /* remoteTransition */, displayChange); if (t != null) { mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); - if (getRotation() != getWindowConfiguration().getRotation()) { + if (isRotationChanging()) { mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN); controller.mTransitionMetricsReporter.associate(t, startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN)); diff --git a/services/core/java/com/android/server/wm/FadeAnimationController.java b/services/core/java/com/android/server/wm/FadeAnimationController.java index 817b27a55c1b8..561a07061bb45 100644 --- a/services/core/java/com/android/server/wm/FadeAnimationController.java +++ b/services/core/java/com/android/server/wm/FadeAnimationController.java @@ -21,7 +21,6 @@ import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION; import android.annotation.NonNull; import android.content.Context; -import android.util.ArrayMap; import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; import android.view.animation.Animation; @@ -38,7 +37,6 @@ import java.io.PrintWriter; public class FadeAnimationController { protected final DisplayContent mDisplayContent; protected final Context mContext; - protected final ArrayMap mDeferredFinishCallbacks = new ArrayMap<>(); public FadeAnimationController(DisplayContent displayContent) { mDisplayContent = displayContent; @@ -78,16 +76,8 @@ public class FadeAnimationController { return; } - // We deferred the end of the animation when hiding the token, so we need to end it now that - // it's shown again. - final SurfaceAnimator.OnAnimationFinishedCallback finishedCallback = show ? (t, r) -> { - final Runnable runnable = mDeferredFinishCallbacks.remove(windowToken); - if (runnable != null) { - runnable.run(); - } - } : null; windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter, - show /* hidden */, animationType, finishedCallback); + show /* hidden */, animationType, null /* finishedCallback */); } protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec, @@ -135,7 +125,7 @@ public class FadeAnimationController { }; } - protected class FadeAnimationAdapter extends LocalAnimationAdapter { + protected static class FadeAnimationAdapter extends LocalAnimationAdapter { protected final boolean mShow; protected final WindowToken mToken; @@ -149,13 +139,10 @@ public class FadeAnimationController { @Override public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) { - // We defer the end of the hide animation to ensure the tokens stay hidden until - // we show them again. - if (!mShow) { - mDeferredFinishCallbacks.put(mToken, endDeferFinishCallback); - return true; - } - return false; + // Defer the finish callback (restore leash) of the hide animation to ensure the token + // stay hidden until it needs to show again. Besides, when starting the show animation, + // the previous hide animation will be cancelled, so the callback can be ignored. + return !mShow; } } } diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java index cf36c85ebabf7..bbda577e9c572 100644 --- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java +++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java @@ -50,7 +50,7 @@ public class FadeRotationAnimationController extends FadeAnimationController { /** Whether to use constant zero alpha animation. */ private boolean mHideImmediately; - /** Whether this controller is triggered from shell transition. */ + /** Whether this controller is triggered from shell transition with type CHANGE. */ private final boolean mIsChangeTransition; /** Whether the start transaction of the transition is committed (by shell). */ @@ -59,21 +59,30 @@ public class FadeRotationAnimationController extends FadeAnimationController { /** The list to store the drawn tokens before the rotation animation starts. */ private ArrayList mPendingShowTokens; + /** It is used when the display has rotated, but some windows fade out in old rotation. */ + private SeamlessRotator mRotator; + + private final int mOriginalRotation; + private final boolean mHasScreenRotationAnimation; + public FadeRotationAnimationController(DisplayContent displayContent) { super(displayContent); mService = displayContent.mWmService; - mIsChangeTransition = displayContent.inTransition() - && displayContent.mTransitionController.getCollectingTransitionType() - == WindowManager.TRANSIT_CHANGE; + mOriginalRotation = displayContent.getWindowConfiguration().getRotation(); + final int transitionType = + displayContent.mTransitionController.getCollectingTransitionType(); + mIsChangeTransition = transitionType == WindowManager.TRANSIT_CHANGE; + // Only CHANGE type (rotation animation) needs to wait for the start transaction. mIsStartTransactionCommitted = !mIsChangeTransition; - mTimeoutRunnable = displayContent.getRotationAnimation() != null - || mIsChangeTransition ? () -> { + mTimeoutRunnable = displayContent.inTransition() ? () -> { synchronized (mService.mGlobalLock) { displayContent.finishFadeRotationAnimationIfPossible(); mService.mWindowPlacerLocked.performSurfacePlacement(); } } : null; - if (mTimeoutRunnable != null) { + mHasScreenRotationAnimation = + displayContent.getRotationAnimation() != null || mIsChangeTransition; + if (mHasScreenRotationAnimation) { // Hide the windows immediately because screen should have been covered by screenshot. mHideImmediately = true; } @@ -103,6 +112,19 @@ public class FadeRotationAnimationController extends FadeAnimationController { }, true /* traverseTopToBottom */); } + @Override + public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) { + if (show) { + final SurfaceControl leash = mTargetWindowTokens.remove(windowToken); + if (leash != null && mRotator != null) { + // The leash was unrotated by start transaction of transition. Clear the transform + // to reshow the window in current rotation. + mRotator.setIdentityMatrix(mDisplayContent.getPendingTransaction(), leash); + } + } + super.fadeWindowToken(show, windowToken, animationType); + } + /** Applies show animation on the previously hidden window tokens. */ void show() { for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { @@ -125,19 +147,23 @@ public class FadeRotationAnimationController extends FadeAnimationController { * controller is created for normal rotation. */ boolean show(WindowToken token) { + if (!isTargetToken(token)) return false; if (!mIsStartTransactionCommitted) { // The fade-in animation should only start after the screenshot layer is shown by shell. // Otherwise the window will be blinking before the rotation animation starts. So store // to a pending list and animate them until the transaction is committed. - if (mTargetWindowTokens.containsKey(token)) { - if (mPendingShowTokens == null) { - mPendingShowTokens = new ArrayList<>(); - } - mPendingShowTokens.add(token); + if (mPendingShowTokens == null) { + mPendingShowTokens = new ArrayList<>(); } + mPendingShowTokens.add(token); return false; } - if (mTimeoutRunnable != null && mTargetWindowTokens.remove(token) != null) { + if (!mHasScreenRotationAnimation && token.mTransitionController.inTransition()) { + // Defer showing to onTransitionFinished(). + return false; + } + // If the timeout runnable is null (fixed rotation), the case will be handled by show(). + if (mTimeoutRunnable != null) { fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM); if (mTargetWindowTokens.isEmpty()) { mService.mH.removeCallbacks(mTimeoutRunnable); @@ -177,6 +203,15 @@ public class FadeRotationAnimationController extends FadeAnimationController { return mTargetWindowTokens.containsKey(token); } + /** + * Whether the insets animation leash should use previous position when running fade out + * animation in rotated display. + */ + boolean shouldFreezeInsetsPosition(WindowState w) { + return !mHasScreenRotationAnimation && w.mTransitionController.inTransition() + && isTargetToken(w.mToken); + } + void setOnShowRunnable(Runnable onShowRunnable) { mOnShowRunnable = onShowRunnable; } @@ -186,6 +221,22 @@ public class FadeRotationAnimationController extends FadeAnimationController { * transition starts. And associate transaction callback to consume pending animations. */ void setupStartTransaction(SurfaceControl.Transaction t) { + if (!mIsChangeTransition) { + // Take OPEN/CLOSE transition type as the example, the non-activity windows need to + // fade out in previous rotation while display has rotated to the new rotation, so + // their leashes are unrotated with the start transaction. + mRotator = new SeamlessRotator(mOriginalRotation, + mDisplayContent.getWindowConfiguration().getRotation(), + mDisplayContent.getDisplayInfo(), + false /* applyFixedTransformationHint */); + for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { + final SurfaceControl leash = mTargetWindowTokens.valueAt(i); + if (leash != null) { + mRotator.applyTransform(t, leash); + } + } + return; + } // Hide the windows immediately because a screenshot layer should cover the screen. for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { final SurfaceControl leash = mTargetWindowTokens.valueAt(i); @@ -208,9 +259,30 @@ public class FadeRotationAnimationController extends FadeAnimationController { }); } + void onTransitionFinished() { + if (mIsChangeTransition) { + // With screen rotation animation, the windows are always faded in when they are drawn. + // Because if they are drawn fast enough, the fade animation should not be observable. + return; + } + // For other transition types, the fade-in animation runs after the transition to make the + // transition animation (e.g. launch activity) look cleaner. + for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { + final WindowToken token = mTargetWindowTokens.keyAt(i); + for (int j = token.getChildCount() - 1; j >= 0; j--) { + // Only fade in the drawn windows. If the remaining windows are drawn later, + // show(WindowToken) will be called to fade in them. + if (token.getChildAt(j).isDrawFinishedLw()) { + mDisplayContent.finishFadeRotationAnimation(token); + break; + } + } + } + } + @Override public Animation getFadeInAnimation() { - if (mTimeoutRunnable != null) { + if (mHasScreenRotationAnimation) { // Use a shorter animation so it is easier to align with screen rotation animation. return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter); } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index af917264698e9..a8a923140a411 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -297,6 +297,14 @@ class InsetsSourceProvider { } private Point getWindowFrameSurfacePosition() { + if (mControl != null) { + final FadeRotationAnimationController fadeController = + mWin.mDisplayContent.getFadeRotationAnimationController(); + if (fadeController != null && fadeController.shouldFreezeInsetsPosition(mWin)) { + // Use previous position because the fade-out animation runs in old rotation. + return mControl.getSurfacePosition(); + } + } final Rect frame = mWin.getFrame(); final Point position = new Point(); mWin.transformFrameToSurfacePosition(frame.left, frame.top, position); diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java index af8293aab977d..80f2ab62f120e 100644 --- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java +++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java @@ -95,14 +95,6 @@ public class NavBarFadeAnimationController extends FadeAnimationController{ } else { fadeAnim.run(); } - } else { - // If fade rotation animation is running and controlling the nav bar, make sure we empty - // the mDeferredFinishCallbacks and defer the runnable until fade rotation animation - // finishes. - final Runnable runnable = mDeferredFinishCallbacks.remove(mNavigationBar.mToken); - if (runnable != null) { - controller.setOnShowRunnable(runnable); - } } } diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java index 4cc369f0a187b..c20b85858c443 100644 --- a/services/core/java/com/android/server/wm/SeamlessRotator.java +++ b/services/core/java/com/android/server/wm/SeamlessRotator.java @@ -20,7 +20,6 @@ import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import android.graphics.Matrix; -import android.os.IBinder; import android.view.DisplayInfo; import android.view.Surface.Rotation; import android.view.SurfaceControl; @@ -73,7 +72,7 @@ public class SeamlessRotator { * global display rotation. */ public void unrotate(Transaction transaction, WindowContainer win) { - transaction.setMatrix(win.getSurfaceControl(), mTransform, mFloat9); + applyTransform(transaction, win.getSurfaceControl()); // WindowState sets the position of the window so transform the position and update it. final float[] winSurfacePos = {win.mLastSurfacePosition.x, win.mLastSurfacePosition.y}; mTransform.mapPoints(winSurfacePos); @@ -83,6 +82,10 @@ public class SeamlessRotator { } } + void applyTransform(Transaction t, SurfaceControl sc) { + t.setMatrix(sc, mTransform, mFloat9); + } + /** * Returns the rotation of the display before it started rotating. * @@ -106,14 +109,17 @@ public class SeamlessRotator { return; } - mTransform.reset(); - t.setMatrix(win.mSurfaceControl, mTransform, mFloat9); + setIdentityMatrix(t, win.mSurfaceControl); t.setPosition(win.mSurfaceControl, win.mLastSurfacePosition.x, win.mLastSurfacePosition.y); if (mApplyFixedTransformHint) { t.unsetFixedTransformHint(win.mSurfaceControl); } } + void setIdentityMatrix(Transaction t, SurfaceControl sc) { + t.setMatrix(sc, Matrix.IDENTITY_MATRIX, mFloat9); + } + public void dump(PrintWriter pw) { pw.print("{old="); pw.print(mOldRotation); pw.print(", new="); pw.print(mNewRotation); pw.print("}"); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index dae004dd5f6f0..c7c3bb6b31e26 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -143,6 +143,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe /** The final animation targets derived from participants after promotion. */ private ArraySet mTargets = null; + /** The main display running this transition. */ + private DisplayContent mTargetDisplay; + /** * Set of participating windowtokens (activity/wallpaper) which are visible at the end of * the transition animation. @@ -473,6 +476,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId); dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */); } + + final FadeRotationAnimationController fadeRotationController = + mTargetDisplay.getFadeRotationAnimationController(); + if (fadeRotationController != null) { + fadeRotationController.onTransitionFinished(); + } } void abort() { @@ -514,6 +523,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } if (dc == null) dc = mController.mAtm.mRootWindowContainer.getDefaultDisplay(); + mTargetDisplay = dc; if (mState == STATE_ABORT) { mController.abort(this); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index a97c0571257ed..ec6cd92493172 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -524,6 +524,43 @@ public class TransitionTests extends WindowTestsBase { assertNull(mDisplayContent.getFadeRotationAnimationController()); } + @Test + public void testAppTransitionWithRotationChange() { + final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar"); + makeWindowVisible(statusBar); + mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs); + final ActivityRecord app = createActivityRecord(mDisplayContent); + final TestTransitionPlayer player = registerTestTransitionPlayer(); + final Transition transition = app.mTransitionController.createTransition(TRANSIT_OPEN); + app.mTransitionController.requestStartTransition(transition, app.getTask(), + null /* remoteTransition */, null /* displayChange */); + mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1); + final int anyChanges = 1; + mDisplayContent.requestChangeTransitionIfNeeded(anyChanges, null /* displayChange */); + transition.setKnownConfigChanges(mDisplayContent, anyChanges); + final FadeRotationAnimationController fadeController = + mDisplayContent.getFadeRotationAnimationController(); + assertNotNull(fadeController); + assertTrue(fadeController.shouldFreezeInsetsPosition(statusBar)); + + statusBar.setOrientationChanging(true); + player.startTransition(); + // Non-app windows should not be collected. + assertFalse(statusBar.mToken.inTransition()); + assertTrue(app.getTask().inTransition()); + + final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class); + player.onTransactionReady(startTransaction); + // The leash should be unrotated. + verify(startTransaction).setMatrix(eq(statusBar.mToken.getAnimationLeash()), any(), any()); + + // The redrawn window will be faded in when the transition finishes. And because this test + // only use one non-activity window, the fade rotation controller should also be cleared. + statusBar.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; + player.finish(); + assertNull(mDisplayContent.getFadeRotationAnimationController()); + } + @Test public void testIntermediateVisibility() { final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);