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:
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user