From 77b2b34f4ef86602114101c40c7229f200c51077 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Thu, 24 Feb 2022 18:46:30 +0800 Subject: [PATCH] Tweak animation for non-app windows with shell fixed rotation To make it have the same behavior as legacy transition: The bars are faded out when starting fixed rotation, and faded in after display rotation change is completed. And the independent seamless window such as round corner can keep showing on screen seamlessly. Also skip animation for seamless display change. Otherwise there will be a no-op alpha animation with DEFAULT_APP_TRANSITION_DURATION=336ms that adds unnecessary delay to finish the transition. Bug: 221223435 Test: atest TransitionTests#testAppTransitionWithRotationChange Change-Id: I3b1db4956b642fabb6061c7dbf0f7b2b8e1e300f --- .../transition/DefaultTransitionHandler.java | 7 ++- .../server/wm/AsyncRotationController.java | 49 ++++++++++------ .../com/android/server/wm/DisplayContent.java | 31 ++++++---- .../com/android/server/wm/Transition.java | 4 +- .../server/wm/TransitionController.java | 5 -- .../android/server/wm/TransitionTests.java | 56 ++++++++++++++++++- 6 files changed, 113 insertions(+), 39 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 4bc5850cd2933..56d51687603a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -348,12 +348,13 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); final boolean isTask = change.getTaskInfo() != null; + boolean isSeamlessDisplayChange = false; if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) { if (info.getType() == TRANSIT_CHANGE) { - boolean isSeamless = isRotationSeamless(info, mDisplayController); + isSeamlessDisplayChange = isRotationSeamless(info, mDisplayController); final int anim = getRotationAnimation(info); - if (!(isSeamless || anim == ROTATION_ANIMATION_JUMPCUT)) { + if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) { mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession, mTransactionPool, startTransaction, change, info.getRootLeash(), anim); @@ -387,6 +388,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { startTransaction.setPosition(change.getLeash(), change.getEndAbsBounds().left - info.getRootOffset().x, change.getEndAbsBounds().top - info.getRootOffset().y); + // Seamless display transition doesn't need to animate. + if (isSeamlessDisplayChange) continue; if (isTask) { // Skip non-tasks since those usually have null bounds. startTransaction.setWindowCrop(change.getLeash(), diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index 5c13e8192701c..9e889ad11b8e5 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -94,6 +94,9 @@ class AsyncRotationController extends FadeAnimationController implements Consume /** Whether the start transaction of the transition is committed (by shell). */ private boolean mIsStartTransactionCommitted; + /** Whether the target windows have been requested to sync their draw transactions. */ + private boolean mIsSyncDrawRequested; + private SeamlessRotator mRotator; private final int mOriginalRotation; @@ -139,22 +142,10 @@ class AsyncRotationController extends FadeAnimationController implements Consume displayContent.forAllWindows(this, true /* traverseTopToBottom */); // Legacy animation doesn't need to wait for the start transaction. - mIsStartTransactionCommitted = mTransitionOp == OP_LEGACY; - if (mIsStartTransactionCommitted) return; - // 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. - for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { - if (mHasScreenRotationAnimation - && mTargetWindowTokens.valueAt(i).mAction == Operation.ACTION_FADE) { - // The windows are hidden (leash is alpha 0) before finishing drawing so it is - // unnecessary to request sync. - continue; - } - final WindowToken token = mTargetWindowTokens.keyAt(i); - for (int j = token.getChildCount() - 1; j >= 0; j--) { - token.getChildAt(j).applyWithNextDraw(t -> {}); - } + if (mTransitionOp == OP_LEGACY) { + mIsStartTransactionCommitted = true; + } else if (displayContent.mTransitionController.useShellTransitionsRotation()) { + keepAppearanceInPreviousRotation(); } } @@ -194,6 +185,30 @@ class AsyncRotationController extends FadeAnimationController implements Consume mTargetWindowTokens.put(w.mToken, new Operation(action)); } + /** + * Enables {@link #handleFinishDrawing(WindowState, SurfaceControl.Transaction)} to capture the + * draw transactions of the target windows if needed. + */ + void keepAppearanceInPreviousRotation() { + // 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. + for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { + if (mHasScreenRotationAnimation + && mTargetWindowTokens.valueAt(i).mAction == Operation.ACTION_FADE) { + // The windows are hidden (leash is alpha 0) before finishing drawing so it is + // unnecessary to request sync. + continue; + } + final WindowToken token = mTargetWindowTokens.keyAt(i); + for (int j = token.getChildCount() - 1; j >= 0; j--) { + token.getChildAt(j).applyWithNextDraw(t -> {}); + } + } + mIsSyncDrawRequested = true; + if (DEBUG) Slog.d(TAG, "Requested to sync draw transaction"); + } + /** Lets the window fit in new rotation naturally. */ private void finishOp(WindowToken windowToken) { final Operation op = mTargetWindowTokens.remove(windowToken); @@ -433,7 +448,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume */ boolean handleFinishDrawing(WindowState w, SurfaceControl.Transaction postDrawTransaction) { if (mTransitionOp == OP_LEGACY || postDrawTransaction == null - || !w.mTransitionController.inTransition()) { + || !mIsSyncDrawRequested || !w.mTransitionController.inTransition()) { return false; } final Operation op = mTargetWindowTokens.get(w.mToken); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 159c71b809558..e2e42a10cd2df 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1736,19 +1736,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) { - final boolean useAsyncRotation = !mTransitionController.isShellTransitionsEnabled(); if (mFixedRotationLaunchingApp == null && r != null) { - mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, - rotation); - if (useAsyncRotation) { - startAsyncRotation( - // Delay the hide animation to avoid blinking by clicking navigation bar - // that may toggle fixed rotation in a short time. - r == mFixedRotationTransitionListener.mAnimatingRecents); - } + mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation); + // Delay the hide animation to avoid blinking by clicking navigation bar that may + // toggle fixed rotation in a short time. + final boolean shouldDebounce = r == mFixedRotationTransitionListener.mAnimatingRecents + || mTransitionController.isTransientLaunch(r); + startAsyncRotation(shouldDebounce); } else if (mFixedRotationLaunchingApp != null && r == null) { mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this); - if (useAsyncRotation) finishAsyncRotationIfPossible(); + // Keep async rotation controller if the next transition of display is requested. + if (!mTransitionController.isCollecting(this)) { + finishAsyncRotationIfPossible(); + } } mFixedRotationLaunchingApp = r; } @@ -1806,7 +1806,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } // Update directly because the app which will change the orientation of display is ready. if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) { - mTransitionController.setSeamlessRotation(this); sendNewConfiguration(); return; } @@ -3235,7 +3234,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp this, this, null /* remoteTransition */, displayChange); if (t != null) { mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); - if (isRotationChanging()) { + if (mFixedRotationLaunchingApp != null) { + // A fixed-rotation transition is done, then continue to start a seamless display + // transition. And be fore the start transaction is applied, the non-app windows + // need to keep in previous rotation to avoid showing inconsistent content. + t.setSeamlessRotation(this); + if (mAsyncRotationController != null) { + mAsyncRotationController.keepAppearanceInPreviousRotation(); + } + } else 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/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 0f5828c7efb02..785867b391ca8 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -548,7 +548,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe for (int i = 0; i < mTargetDisplays.size(); ++i) { final DisplayContent dc = mTargetDisplays.get(i); final AsyncRotationController asyncRotationController = dc.getAsyncRotationController(); - if (asyncRotationController != null) { + if (asyncRotationController != null && mTargets.contains(dc)) { asyncRotationController.onTransitionFinished(); } if (mTransientLaunches != null) { @@ -696,7 +696,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // This is non-null only if display has changes. It handles the visible windows that don't // need to be participated in the transition. final AsyncRotationController controller = dc.getAsyncRotationController(); - if (controller != null) { + if (controller != null && mTargets.contains(dc)) { controller.setupStartTransaction(transaction); } mStartTransaction = transaction; diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index b0532c2a15520..0cfc702018a07 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -519,11 +519,6 @@ class TransitionController { } } - void setSeamlessRotation(@NonNull WindowContainer wc) { - if (mCollectingTransition == null) return; - mCollectingTransition.setSeamlessRotation(wc); - } - void legacyDetachNavigationBarFromApp(@NonNull IBinder token) { final Transition transition = Transition.fromBinder(token); if (transition == null || !mPlayingTransitions.contains(transition)) { 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 29289a4047911..32af672c8693b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; @@ -557,11 +558,20 @@ public class TransitionTests extends WindowTestsBase { @Test public void testAppTransitionWithRotationChange() { + final TestTransitionPlayer player = registerTestTransitionPlayer(); + final boolean useFixedRotation = !player.mController.useShellTransitionsRotation(); + if (useFixedRotation) { + testFixedRotationOpen(player); + } else { + testShellRotationOpen(player); + } + } + + private void testShellRotationOpen(TestTransitionPlayer player) { 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 */); @@ -605,6 +615,50 @@ public class TransitionTests extends WindowTestsBase { assertNull(mDisplayContent.getAsyncRotationController()); } + private void testFixedRotationOpen(TestTransitionPlayer player) { + final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar"); + makeWindowVisible(statusBar); + mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs); + final ActivityRecord app = createActivityRecord(mDisplayContent); + final Transition transition = app.mTransitionController.createTransition(TRANSIT_OPEN); + app.mTransitionController.requestStartTransition(transition, app.getTask(), + null /* remoteTransition */, null /* displayChange */); + app.mTransitionController.collectExistenceChange(app.getTask()); + mDisplayContent.setFixedRotationLaunchingAppUnchecked(app); + final AsyncRotationController asyncRotationController = + mDisplayContent.getAsyncRotationController(); + assertNotNull(asyncRotationController); + assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar)); + assertTrue(app.getTask().inTransition()); + + player.start(); + player.finish(); + app.getTask().clearSyncState(); + + // The open transition is finished. Continue to play seamless display change transition, + // so the previous async rotation controller should still exist. + mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1); + mDisplayContent.setLastHasContent(); + mDisplayContent.requestChangeTransitionIfNeeded(1 /* changes */, null /* displayChange */); + assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp()); + assertNotNull(mDisplayContent.getAsyncRotationController()); + + statusBar.setOrientationChanging(true); + player.startTransition(); + // Non-app windows should not be collected. + assertFalse(statusBar.mToken.inTransition()); + + onRotationTransactionReady(player, mWm.mTransactionFactory.get()).onTransactionCommitted(); + assertEquals(ROTATION_ANIMATION_SEAMLESS, player.mLastReady.getChange( + mDisplayContent.mRemoteToken.toWindowContainerToken()).getRotationAnimation()); + player.finish(); + + // The controller should be cleared if the target windows are drawn. + statusBar.finishDrawing(mWm.mTransactionFactory.get()); + statusBar.setOrientationChanging(false); + assertNull(mDisplayContent.getAsyncRotationController()); + } + @Test public void testIntermediateVisibility() { final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);