diff options
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 4bc5850cd293..56d51687603a 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 5c13e8192701..9e889ad11b8e 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 195d4258c0d9..71a1b3e6c5d2 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1735,19 +1735,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; } @@ -1805,7 +1805,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; } @@ -3234,7 +3233,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 0f5828c7efb0..785867b391ca 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 b0532c2a1552..0cfc702018a0 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 29289a404791..32af672c8693 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); |