From 35385b8c7281e95ede1641c3b82387e3a475f66d Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Wed, 29 Dec 2021 23:29:02 -0600 Subject: [PATCH] Synchronize rotating non-app windows with fade animation By capturing the sync draw transaction, the window can keep the appearance in old rotating when running fade out animation. That avoids flickering if the window is drawn earlier with new rotation. Also centralize notifyStartingWindowDrawn to reduce duplicated code and increase consistency of measurement. Bug: 212570760 Test: atest TransitionTests#testAppTransitionWithRotationChange Change-Id: I521b9c4278dd57bb6f277c13e6e7189a0c030e79 --- .../com/android/server/wm/ActivityRecord.java | 1 - .../wm/FadeRotationAnimationController.java | 62 ++++++++++++++++--- .../android/server/wm/WindowContainer.java | 6 +- .../com/android/server/wm/WindowState.java | 37 ++++++----- .../android/server/wm/TransitionTests.java | 9 ++- 5 files changed, 88 insertions(+), 27 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index fcc2bd6d00326..30465aff8cae8 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -6337,7 +6337,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } else if (w.isDrawn()) { // The starting window for this container is drawn. - mTaskSupervisor.getActivityMetricsLogger().notifyStartingWindowDrawn(this); startingDisplayed = true; } } diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java index bbda577e9c572..c85e04dbfa151 100644 --- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java +++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java @@ -59,8 +59,13 @@ 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; + /** + * The sync transactions of the target windows. It is used when the display has rotated but + * the windows need to fade out in previous rotation. These transactions will be applied with + * fade-in animation, so there won't be a flickering such as the windows have redrawn during + * fading out. + */ + private ArrayMap mCapturedDrawTransactions; private final int mOriginalRotation; private final boolean mHasScreenRotationAnimation; @@ -110,16 +115,36 @@ public class FadeRotationAnimationController extends FadeAnimationController { mTargetWindowTokens.put(w.mToken, null); } }, true /* traverseTopToBottom */); + + // The transition sync group may be finished earlier because it doesn't wait for these + // target windows. But the windows still need to use sync transaction to keep the appearance + // in previous rotation, so request a no-op sync to keep the state. + if (!mIsChangeTransition && transitionType != WindowManager.TRANSIT_NONE) { + for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { + final WindowToken token = mTargetWindowTokens.keyAt(i); + for (int j = token.getChildCount() - 1; j >= 0; j--) { + token.getChildAt(j).applyWithNextDraw(t -> {}); + } + } + } } @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); + // The previous animation leash will be dropped when preparing fade-in animation, so + // simply remove it without restoring the transformation. + mTargetWindowTokens.remove(windowToken); + if (mCapturedDrawTransactions != null) { + // Unblock the window to draw its latest content with fade-in animation. + final SurfaceControl.Transaction t = mDisplayContent.getPendingTransaction(); + for (int i = windowToken.getChildCount() - 1; i >= 0; i--) { + final SurfaceControl.Transaction drawT = + mCapturedDrawTransactions.remove(windowToken.getChildAt(i)); + if (drawT != null) { + t.merge(drawT); + } + } } } super.fadeWindowToken(show, windowToken, animationType); @@ -225,14 +250,14 @@ public class FadeRotationAnimationController extends FadeAnimationController { // 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, + final SeamlessRotator rotator = 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); + rotator.applyTransform(t, leash); } } return; @@ -280,6 +305,25 @@ public class FadeRotationAnimationController extends FadeAnimationController { } } + /** Captures the post draw transaction if the window should update with fade-in animation. */ + boolean handleFinishDrawing(WindowState w, SurfaceControl.Transaction postDrawTransaction) { + if (mIsChangeTransition || !isTargetToken(w.mToken)) return false; + if (postDrawTransaction != null && w.mTransitionController.inTransition()) { + if (mCapturedDrawTransactions == null) { + mCapturedDrawTransactions = new ArrayMap<>(); + } + final SurfaceControl.Transaction t = mCapturedDrawTransactions.get(w); + if (t == null) { + mCapturedDrawTransactions.put(w, postDrawTransaction); + } else { + t.merge(postDrawTransaction); + } + return true; + } + mDisplayContent.finishFadeRotationAnimation(w.mToken); + return false; + } + @Override public Animation getFadeInAnimation() { if (mHasScreenRotationAnimation) { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index bef7810f5d48b..61acb97c9db18 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -3354,8 +3354,12 @@ class WindowContainer extends ConfigurationContainer< for (int i = mChildren.size() - 1; i >= 0; --i) { mChildren.get(i).finishSync(outMergedTransaction, cancel); } - mSyncState = SYNC_STATE_NONE; if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this); + clearSyncState(); + } + + void clearSyncState() { + mSyncState = SYNC_STATE_NONE; mSyncGroup = null; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index f6729c552cc31..d8ce500e06480 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5747,29 +5747,36 @@ class WindowState extends WindowContainer implements WindowManagerP Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms"); mActivityRecord.mRelaunchStartTime = 0; } - - executeDrawHandlers(postDrawTransaction); - - final boolean applyPostDrawNow = mClientWasDrawingForSync && postDrawTransaction != null; - mClientWasDrawingForSync = false; - if (!onSyncFinishedDrawing()) { - return mWinAnimator.finishDrawingLocked(postDrawTransaction, applyPostDrawNow); - } - - if (mActivityRecord != null - && mTransitionController.isShellTransitionsEnabled() - && mAttrs.type == TYPE_APPLICATION_STARTING) { + if (mActivityRecord != null && mAttrs.type == TYPE_APPLICATION_STARTING) { mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger() .notifyStartingWindowDrawn(mActivityRecord); } - if (postDrawTransaction != null) { + final boolean hasSyncHandlers = executeDrawHandlers(postDrawTransaction); + + boolean skipLayout = false; + // Control the timing to switch the appearance of window with different rotations. + final FadeRotationAnimationController fadeRotationController = + mDisplayContent.getFadeRotationAnimationController(); + if (fadeRotationController != null + && fadeRotationController.handleFinishDrawing(this, postDrawTransaction)) { + // Consume the transaction because the controller will apply it with fade animation. + // Layout is not needed because the window will be hidden by the fade leash. Clear + // sync state because its sync transaction doesn't need to be merged to sync group. + postDrawTransaction = null; + skipLayout = true; + clearSyncState(); + } else if (onSyncFinishedDrawing() && postDrawTransaction != null) { mSyncTransaction.merge(postDrawTransaction); + // Consume the transaction because the sync group will merge it. + postDrawTransaction = null; } - mWinAnimator.finishDrawingLocked(null, false /* forceApplyNow */); + final boolean layoutNeeded = + mWinAnimator.finishDrawingLocked(postDrawTransaction, mClientWasDrawingForSync); + mClientWasDrawingForSync = false; // We always want to force a traversal after a finish draw for blast sync. - return true; + return !skipLayout && (hasSyncHandlers || layoutNeeded); } void immediatelyNotifyBlastSync() { 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 ec6cd92493172..ed3888c7aedda 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -556,8 +556,15 @@ public class TransitionTests extends WindowTestsBase { // 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; + statusBar.mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING; + final SurfaceControl.Transaction postDrawTransaction = + mock(SurfaceControl.Transaction.class); + final boolean layoutNeeded = statusBar.finishDrawing(postDrawTransaction); + assertFalse(layoutNeeded); player.finish(); + // The controller should capture the draw transaction and merge it when preparing to run + // fade-in animation. + verify(mDisplayContent.getPendingTransaction()).merge(eq(postDrawTransaction)); assertNull(mDisplayContent.getFadeRotationAnimationController()); }