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
This commit is contained in:
Riddle Hsu
2022-02-24 18:46:30 +08:00
parent de4a9a6a06
commit 77b2b34f4e
6 changed files with 113 additions and 39 deletions

View File

@@ -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(),

View File

@@ -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);

View File

@@ -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));

View File

@@ -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;

View File

@@ -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)) {

View File

@@ -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);