Handle non-app windows in app rotation transition asynchronously
When starting activity in different orientation with shell transition,
the display will be rotated immediately. To avoid jump cut of non-app
windows by the change, those windows will be faded out in old rotation.
And when the app transition is done, the windows will be faded in with
current rotation.
This also reduces transition latency because the transition can start
once the activities are ready, without waiting for those non-activity
window to complete redraw.
Bug: 210839369
Test: atest TransitionTests#testAppTransitionWithRotationChange
Test: adb shell setprop persist.debug.shell_transit 1; reboot
Launch landscape app from portrait home.
Change-Id: I0afc3fcdb3b2ee9d23d5d4addc488ae085a2afd8
This commit is contained in:
@@ -1843,6 +1843,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns {@code true} if the decided new rotation has not applied to configuration yet. */
|
||||
private boolean isRotationChanging() {
|
||||
return mDisplayRotation.getRotation() != getWindowConfiguration().getRotation();
|
||||
}
|
||||
|
||||
private void startFadeRotationAnimationIfNeeded() {
|
||||
if (isRotationChanging()) {
|
||||
startFadeRotationAnimation(false /* shouldDebounce */);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the hide animation for the windows which will be rotated seamlessly.
|
||||
*
|
||||
@@ -3202,11 +3213,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
|
||||
// Hide the windows which are not significant in rotation animation. So that the windows
|
||||
// don't need to block the unfreeze time.
|
||||
if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()
|
||||
// Do not fade for freezing without rotation change.
|
||||
&& mDisplayRotation.getRotation() != getWindowConfiguration().getRotation()
|
||||
&& mFadeRotationAnimationController == null) {
|
||||
startFadeRotationAnimation(false /* shouldDebounce */);
|
||||
if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
|
||||
startFadeRotationAnimationIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3227,6 +3235,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
}
|
||||
if (!controller.isCollecting(this)) {
|
||||
controller.collect(this);
|
||||
startFadeRotationAnimationIfNeeded();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -3234,7 +3243,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
this, this, null /* remoteTransition */, displayChange);
|
||||
if (t != null) {
|
||||
mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
|
||||
if (getRotation() != getWindowConfiguration().getRotation()) {
|
||||
if (isRotationChanging()) {
|
||||
mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
|
||||
controller.mTransitionMetricsReporter.associate(t,
|
||||
startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
|
||||
|
||||
@@ -21,7 +21,6 @@ import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.animation.Animation;
|
||||
@@ -38,7 +37,6 @@ import java.io.PrintWriter;
|
||||
public class FadeAnimationController {
|
||||
protected final DisplayContent mDisplayContent;
|
||||
protected final Context mContext;
|
||||
protected final ArrayMap<WindowToken, Runnable> mDeferredFinishCallbacks = new ArrayMap<>();
|
||||
|
||||
public FadeAnimationController(DisplayContent displayContent) {
|
||||
mDisplayContent = displayContent;
|
||||
@@ -78,16 +76,8 @@ public class FadeAnimationController {
|
||||
return;
|
||||
}
|
||||
|
||||
// We deferred the end of the animation when hiding the token, so we need to end it now that
|
||||
// it's shown again.
|
||||
final SurfaceAnimator.OnAnimationFinishedCallback finishedCallback = show ? (t, r) -> {
|
||||
final Runnable runnable = mDeferredFinishCallbacks.remove(windowToken);
|
||||
if (runnable != null) {
|
||||
runnable.run();
|
||||
}
|
||||
} : null;
|
||||
windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter,
|
||||
show /* hidden */, animationType, finishedCallback);
|
||||
show /* hidden */, animationType, null /* finishedCallback */);
|
||||
}
|
||||
|
||||
protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
|
||||
@@ -135,7 +125,7 @@ public class FadeAnimationController {
|
||||
};
|
||||
}
|
||||
|
||||
protected class FadeAnimationAdapter extends LocalAnimationAdapter {
|
||||
protected static class FadeAnimationAdapter extends LocalAnimationAdapter {
|
||||
protected final boolean mShow;
|
||||
protected final WindowToken mToken;
|
||||
|
||||
@@ -149,13 +139,10 @@ public class FadeAnimationController {
|
||||
|
||||
@Override
|
||||
public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
|
||||
// We defer the end of the hide animation to ensure the tokens stay hidden until
|
||||
// we show them again.
|
||||
if (!mShow) {
|
||||
mDeferredFinishCallbacks.put(mToken, endDeferFinishCallback);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// Defer the finish callback (restore leash) of the hide animation to ensure the token
|
||||
// stay hidden until it needs to show again. Besides, when starting the show animation,
|
||||
// the previous hide animation will be cancelled, so the callback can be ignored.
|
||||
return !mShow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class FadeRotationAnimationController extends FadeAnimationController {
|
||||
/** Whether to use constant zero alpha animation. */
|
||||
private boolean mHideImmediately;
|
||||
|
||||
/** Whether this controller is triggered from shell transition. */
|
||||
/** Whether this controller is triggered from shell transition with type CHANGE. */
|
||||
private final boolean mIsChangeTransition;
|
||||
|
||||
/** Whether the start transaction of the transition is committed (by shell). */
|
||||
@@ -59,21 +59,30 @@ public class FadeRotationAnimationController extends FadeAnimationController {
|
||||
/** The list to store the drawn tokens before the rotation animation starts. */
|
||||
private ArrayList<WindowToken> mPendingShowTokens;
|
||||
|
||||
/** It is used when the display has rotated, but some windows fade out in old rotation. */
|
||||
private SeamlessRotator mRotator;
|
||||
|
||||
private final int mOriginalRotation;
|
||||
private final boolean mHasScreenRotationAnimation;
|
||||
|
||||
public FadeRotationAnimationController(DisplayContent displayContent) {
|
||||
super(displayContent);
|
||||
mService = displayContent.mWmService;
|
||||
mIsChangeTransition = displayContent.inTransition()
|
||||
&& displayContent.mTransitionController.getCollectingTransitionType()
|
||||
== WindowManager.TRANSIT_CHANGE;
|
||||
mOriginalRotation = displayContent.getWindowConfiguration().getRotation();
|
||||
final int transitionType =
|
||||
displayContent.mTransitionController.getCollectingTransitionType();
|
||||
mIsChangeTransition = transitionType == WindowManager.TRANSIT_CHANGE;
|
||||
// Only CHANGE type (rotation animation) needs to wait for the start transaction.
|
||||
mIsStartTransactionCommitted = !mIsChangeTransition;
|
||||
mTimeoutRunnable = displayContent.getRotationAnimation() != null
|
||||
|| mIsChangeTransition ? () -> {
|
||||
mTimeoutRunnable = displayContent.inTransition() ? () -> {
|
||||
synchronized (mService.mGlobalLock) {
|
||||
displayContent.finishFadeRotationAnimationIfPossible();
|
||||
mService.mWindowPlacerLocked.performSurfacePlacement();
|
||||
}
|
||||
} : null;
|
||||
if (mTimeoutRunnable != null) {
|
||||
mHasScreenRotationAnimation =
|
||||
displayContent.getRotationAnimation() != null || mIsChangeTransition;
|
||||
if (mHasScreenRotationAnimation) {
|
||||
// Hide the windows immediately because screen should have been covered by screenshot.
|
||||
mHideImmediately = true;
|
||||
}
|
||||
@@ -103,6 +112,19 @@ public class FadeRotationAnimationController extends FadeAnimationController {
|
||||
}, true /* traverseTopToBottom */);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
super.fadeWindowToken(show, windowToken, animationType);
|
||||
}
|
||||
|
||||
/** Applies show animation on the previously hidden window tokens. */
|
||||
void show() {
|
||||
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
||||
@@ -125,19 +147,23 @@ public class FadeRotationAnimationController extends FadeAnimationController {
|
||||
* controller is created for normal rotation.
|
||||
*/
|
||||
boolean show(WindowToken token) {
|
||||
if (!isTargetToken(token)) return false;
|
||||
if (!mIsStartTransactionCommitted) {
|
||||
// The fade-in animation should only start after the screenshot layer is shown by shell.
|
||||
// Otherwise the window will be blinking before the rotation animation starts. So store
|
||||
// to a pending list and animate them until the transaction is committed.
|
||||
if (mTargetWindowTokens.containsKey(token)) {
|
||||
if (mPendingShowTokens == null) {
|
||||
mPendingShowTokens = new ArrayList<>();
|
||||
}
|
||||
mPendingShowTokens.add(token);
|
||||
if (mPendingShowTokens == null) {
|
||||
mPendingShowTokens = new ArrayList<>();
|
||||
}
|
||||
mPendingShowTokens.add(token);
|
||||
return false;
|
||||
}
|
||||
if (mTimeoutRunnable != null && mTargetWindowTokens.remove(token) != null) {
|
||||
if (!mHasScreenRotationAnimation && token.mTransitionController.inTransition()) {
|
||||
// Defer showing to onTransitionFinished().
|
||||
return false;
|
||||
}
|
||||
// If the timeout runnable is null (fixed rotation), the case will be handled by show().
|
||||
if (mTimeoutRunnable != null) {
|
||||
fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
|
||||
if (mTargetWindowTokens.isEmpty()) {
|
||||
mService.mH.removeCallbacks(mTimeoutRunnable);
|
||||
@@ -177,6 +203,15 @@ public class FadeRotationAnimationController extends FadeAnimationController {
|
||||
return mTargetWindowTokens.containsKey(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the insets animation leash should use previous position when running fade out
|
||||
* animation in rotated display.
|
||||
*/
|
||||
boolean shouldFreezeInsetsPosition(WindowState w) {
|
||||
return !mHasScreenRotationAnimation && w.mTransitionController.inTransition()
|
||||
&& isTargetToken(w.mToken);
|
||||
}
|
||||
|
||||
void setOnShowRunnable(Runnable onShowRunnable) {
|
||||
mOnShowRunnable = onShowRunnable;
|
||||
}
|
||||
@@ -186,6 +221,22 @@ public class FadeRotationAnimationController extends FadeAnimationController {
|
||||
* transition starts. And associate transaction callback to consume pending animations.
|
||||
*/
|
||||
void setupStartTransaction(SurfaceControl.Transaction t) {
|
||||
if (!mIsChangeTransition) {
|
||||
// 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,
|
||||
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);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Hide the windows immediately because a screenshot layer should cover the screen.
|
||||
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
||||
final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
|
||||
@@ -208,9 +259,30 @@ public class FadeRotationAnimationController extends FadeAnimationController {
|
||||
});
|
||||
}
|
||||
|
||||
void onTransitionFinished() {
|
||||
if (mIsChangeTransition) {
|
||||
// With screen rotation animation, the windows are always faded in when they are drawn.
|
||||
// Because if they are drawn fast enough, the fade animation should not be observable.
|
||||
return;
|
||||
}
|
||||
// For other transition types, the fade-in animation runs after the transition to make the
|
||||
// transition animation (e.g. launch activity) look cleaner.
|
||||
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
|
||||
final WindowToken token = mTargetWindowTokens.keyAt(i);
|
||||
for (int j = token.getChildCount() - 1; j >= 0; j--) {
|
||||
// Only fade in the drawn windows. If the remaining windows are drawn later,
|
||||
// show(WindowToken) will be called to fade in them.
|
||||
if (token.getChildAt(j).isDrawFinishedLw()) {
|
||||
mDisplayContent.finishFadeRotationAnimation(token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animation getFadeInAnimation() {
|
||||
if (mTimeoutRunnable != null) {
|
||||
if (mHasScreenRotationAnimation) {
|
||||
// Use a shorter animation so it is easier to align with screen rotation animation.
|
||||
return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
|
||||
}
|
||||
|
||||
@@ -297,6 +297,14 @@ class InsetsSourceProvider {
|
||||
}
|
||||
|
||||
private Point getWindowFrameSurfacePosition() {
|
||||
if (mControl != null) {
|
||||
final FadeRotationAnimationController fadeController =
|
||||
mWin.mDisplayContent.getFadeRotationAnimationController();
|
||||
if (fadeController != null && fadeController.shouldFreezeInsetsPosition(mWin)) {
|
||||
// Use previous position because the fade-out animation runs in old rotation.
|
||||
return mControl.getSurfacePosition();
|
||||
}
|
||||
}
|
||||
final Rect frame = mWin.getFrame();
|
||||
final Point position = new Point();
|
||||
mWin.transformFrameToSurfacePosition(frame.left, frame.top, position);
|
||||
|
||||
@@ -95,14 +95,6 @@ public class NavBarFadeAnimationController extends FadeAnimationController{
|
||||
} else {
|
||||
fadeAnim.run();
|
||||
}
|
||||
} else {
|
||||
// If fade rotation animation is running and controlling the nav bar, make sure we empty
|
||||
// the mDeferredFinishCallbacks and defer the runnable until fade rotation animation
|
||||
// finishes.
|
||||
final Runnable runnable = mDeferredFinishCallbacks.remove(mNavigationBar.mToken);
|
||||
if (runnable != null) {
|
||||
controller.setOnShowRunnable(runnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import static android.view.Surface.ROTATION_270;
|
||||
import static android.view.Surface.ROTATION_90;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
import android.os.IBinder;
|
||||
import android.view.DisplayInfo;
|
||||
import android.view.Surface.Rotation;
|
||||
import android.view.SurfaceControl;
|
||||
@@ -73,7 +72,7 @@ public class SeamlessRotator {
|
||||
* global display rotation.
|
||||
*/
|
||||
public void unrotate(Transaction transaction, WindowContainer win) {
|
||||
transaction.setMatrix(win.getSurfaceControl(), mTransform, mFloat9);
|
||||
applyTransform(transaction, win.getSurfaceControl());
|
||||
// WindowState sets the position of the window so transform the position and update it.
|
||||
final float[] winSurfacePos = {win.mLastSurfacePosition.x, win.mLastSurfacePosition.y};
|
||||
mTransform.mapPoints(winSurfacePos);
|
||||
@@ -83,6 +82,10 @@ public class SeamlessRotator {
|
||||
}
|
||||
}
|
||||
|
||||
void applyTransform(Transaction t, SurfaceControl sc) {
|
||||
t.setMatrix(sc, mTransform, mFloat9);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rotation of the display before it started rotating.
|
||||
*
|
||||
@@ -106,14 +109,17 @@ public class SeamlessRotator {
|
||||
return;
|
||||
}
|
||||
|
||||
mTransform.reset();
|
||||
t.setMatrix(win.mSurfaceControl, mTransform, mFloat9);
|
||||
setIdentityMatrix(t, win.mSurfaceControl);
|
||||
t.setPosition(win.mSurfaceControl, win.mLastSurfacePosition.x, win.mLastSurfacePosition.y);
|
||||
if (mApplyFixedTransformHint) {
|
||||
t.unsetFixedTransformHint(win.mSurfaceControl);
|
||||
}
|
||||
}
|
||||
|
||||
void setIdentityMatrix(Transaction t, SurfaceControl sc) {
|
||||
t.setMatrix(sc, Matrix.IDENTITY_MATRIX, mFloat9);
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw) {
|
||||
pw.print("{old="); pw.print(mOldRotation); pw.print(", new="); pw.print(mNewRotation);
|
||||
pw.print("}");
|
||||
|
||||
@@ -143,6 +143,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
|
||||
/** The final animation targets derived from participants after promotion. */
|
||||
private ArraySet<WindowContainer> mTargets = null;
|
||||
|
||||
/** The main display running this transition. */
|
||||
private DisplayContent mTargetDisplay;
|
||||
|
||||
/**
|
||||
* Set of participating windowtokens (activity/wallpaper) which are visible at the end of
|
||||
* the transition animation.
|
||||
@@ -473,6 +476,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
|
||||
mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
|
||||
dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
|
||||
}
|
||||
|
||||
final FadeRotationAnimationController fadeRotationController =
|
||||
mTargetDisplay.getFadeRotationAnimationController();
|
||||
if (fadeRotationController != null) {
|
||||
fadeRotationController.onTransitionFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void abort() {
|
||||
@@ -514,6 +523,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
|
||||
}
|
||||
}
|
||||
if (dc == null) dc = mController.mAtm.mRootWindowContainer.getDefaultDisplay();
|
||||
mTargetDisplay = dc;
|
||||
|
||||
if (mState == STATE_ABORT) {
|
||||
mController.abort(this);
|
||||
|
||||
@@ -524,6 +524,43 @@ public class TransitionTests extends WindowTestsBase {
|
||||
assertNull(mDisplayContent.getFadeRotationAnimationController());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppTransitionWithRotationChange() {
|
||||
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 */);
|
||||
mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
|
||||
final int anyChanges = 1;
|
||||
mDisplayContent.requestChangeTransitionIfNeeded(anyChanges, null /* displayChange */);
|
||||
transition.setKnownConfigChanges(mDisplayContent, anyChanges);
|
||||
final FadeRotationAnimationController fadeController =
|
||||
mDisplayContent.getFadeRotationAnimationController();
|
||||
assertNotNull(fadeController);
|
||||
assertTrue(fadeController.shouldFreezeInsetsPosition(statusBar));
|
||||
|
||||
statusBar.setOrientationChanging(true);
|
||||
player.startTransition();
|
||||
// Non-app windows should not be collected.
|
||||
assertFalse(statusBar.mToken.inTransition());
|
||||
assertTrue(app.getTask().inTransition());
|
||||
|
||||
final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
|
||||
player.onTransactionReady(startTransaction);
|
||||
// The leash should be unrotated.
|
||||
verify(startTransaction).setMatrix(eq(statusBar.mToken.getAnimationLeash()), any(), any());
|
||||
|
||||
// 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;
|
||||
player.finish();
|
||||
assertNull(mDisplayContent.getFadeRotationAnimationController());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntermediateVisibility() {
|
||||
final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
|
||||
|
||||
Reference in New Issue
Block a user