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 e8f2e9e710905..863e3ca3bc28e 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()); }