Merge "Handle non-app windows in app rotation transition asynchronously"

This commit is contained in:
Riddle Hsu
2021-12-16 12:01:46 +00:00
committed by Android (Google) Code Review
8 changed files with 172 additions and 51 deletions

View File

@@ -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.
*
@@ -3189,11 +3200,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();
}
}
@@ -3214,6 +3222,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
if (!controller.isCollecting(this)) {
controller.collect(this);
startFadeRotationAnimationIfNeeded();
}
return;
}
@@ -3221,7 +3230,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));

View File

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

View File

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

View File

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

View File

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

View File

@@ -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("}");

View File

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

View File

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