From c3339789f6c99112e9eff10432dc7223fb9a9b84 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Wed, 1 Dec 2021 01:19:08 -0600 Subject: [PATCH] Migrate hybrid rotation animation with shell transition This makes commit 1a7dc4c support shell transition for the case of display rotation with CHANGE type transition. To reduce the latency of rotation animation by not waiting for some insignificant windows. An example: 1. Rotation changed, request change transition. 2. Collect the windows that don't need to be synced with transition into fade controller (the windows still have state SYNC_STATE_WAITING_FOR_DRAW) and create animation leash for them. Assume there are X and Y. 3. Start transition, that collects visible task and visible non-app window tokens which won't be handled by the fade controller. 4. The participated windows are drawn, so onTransactionReady is called and send the start transaction to shell. 5. X reported drawn. But because the start transaction has not committed, postpone to execute the animation. Otherwise the fade-in animation shows before rotation animation that will look flickering. 6. Shell applies the start transaction, which includes - Merged sync transaction (includes X,Y). - Show rotation layer (screenshot). - Hide the leash of X,Y (It is usually covered by screenshot, just in case to ensure smooth fade-in). 7. The commit callback of start transaction is called. Start to fade in X (It is usually unobservable because rotation animation is also running). 8. The rotation animation finished (finishTransition). 9. Y is really slow and finished now. Fade in it directly. This fade animation will be observable. Bug: 206487939 Test: TransitionTests#testDisplayRotationChange DisplayContentTests#testHybridRotationAnimation Change-Id: If4635abc7ef57cac8a925d9613d988ef3ade62b7 --- .../com/android/server/wm/DisplayContent.java | 16 +-- .../android/server/wm/DisplayRotation.java | 8 +- .../server/wm/FadeAnimationController.java | 23 ++-- .../wm/FadeRotationAnimationController.java | 114 +++++++++++++++--- .../server/wm/InsetsStateController.java | 10 -- .../wm/NavBarFadeAnimationController.java | 14 +-- .../com/android/server/wm/Transition.java | 6 + .../server/wm/TransitionController.java | 23 ++++ .../server/wm/WindowOrganizerController.java | 5 +- .../com/android/server/wm/WindowState.java | 4 +- .../server/wm/WindowStateAnimator.java | 2 +- .../com/android/server/wm/WindowToken.java | 7 +- .../server/wm/DisplayContentTests.java | 7 +- .../android/server/wm/TransitionTests.java | 55 +++++++++ .../android/server/wm/WindowTestsBase.java | 13 +- 15 files changed, 223 insertions(+), 84 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 5b3cd423fce72..39bcf5a30cc0f 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1588,8 +1588,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Override boolean isSyncFinished() { - if (mDisplayRotation.isWaitingForRemoteRotation()) return false; - return super.isSyncFinished(); + // Do not consider children because if they are requested to be synced, they should be + // added to sync group explicitly. + return !mDisplayRotation.isWaitingForRemoteRotation(); } /** @@ -1879,16 +1880,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } - /** Shows the given window which may be hidden for screen frozen. */ - void finishFadeRotationAnimation(WindowState w) { + /** Shows the given window which may be hidden for screen rotation. */ + void finishFadeRotationAnimation(WindowToken windowToken) { final FadeRotationAnimationController controller = mFadeRotationAnimationController; - if (controller != null && controller.show(w.mToken)) { + if (controller != null && controller.show(windowToken)) { mFadeRotationAnimationController = null; } } - /** Returns {@code true} if the display should wait for the given window to stop freezing. */ - boolean waitForUnfreeze(WindowState w) { + /** Returns {@code true} if the screen rotation animation needs to wait for the window. */ + boolean shouldSyncRotationChange(WindowState w) { if (w.mForceSeamlesslyRotate) { // The window should look no different before and after rotation. return false; @@ -3233,6 +3234,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN); controller.mTransitionMetricsReporter.associate(t, startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN)); + startFadeRotationAnimation(false /* shouldDebounce */); } t.setKnownConfigChanges(this, changes); } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 427bbeb78fb03..32004730bde90 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -595,12 +595,8 @@ public class DisplayRotation { // Go through all tasks and collect them before the rotation // TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper // handling is synchronized. - mDisplayContent.forAllTasks(task -> { - if (task.isVisible()) { - mDisplayContent.mTransitionController.collect(task); - } - }); - mDisplayContent.getInsetsStateController().addProvidersToTransition(); + mDisplayContent.mTransitionController.collectForDisplayChange(mDisplayContent, + null /* use collecting transition */); } mService.mAtmService.deferWindowLayout(); try { diff --git a/services/core/java/com/android/server/wm/FadeAnimationController.java b/services/core/java/com/android/server/wm/FadeAnimationController.java index 2f3ad4080ec93..817b27a55c1b8 100644 --- a/services/core/java/com/android/server/wm/FadeAnimationController.java +++ b/services/core/java/com/android/server/wm/FadeAnimationController.java @@ -36,10 +36,12 @@ import java.io.PrintWriter; * An animation controller to fade-in/out for a window token. */ public class FadeAnimationController { + protected final DisplayContent mDisplayContent; protected final Context mContext; protected final ArrayMap mDeferredFinishCallbacks = new ArrayMap<>(); public FadeAnimationController(DisplayContent displayContent) { + mDisplayContent = displayContent; mContext = displayContent.mWmService.mContext; } @@ -69,7 +71,9 @@ public class FadeAnimationController { return; } - final FadeAnimationAdapter animationAdapter = createAdapter(show, windowToken); + final Animation animation = show ? getFadeInAnimation() : getFadeOutAnimation(); + final FadeAnimationAdapter animationAdapter = animation != null + ? createAdapter(createAnimationSpec(animation), show, windowToken) : null; if (animationAdapter == null) { return; } @@ -86,17 +90,10 @@ public class FadeAnimationController { show /* hidden */, animationType, finishedCallback); } - protected FadeAnimationAdapter createAdapter(boolean show, WindowToken windowToken) { - final Animation animation = show ? getFadeInAnimation() : getFadeOutAnimation(); - if (animation == null) { - return null; - } - - final LocalAnimationAdapter.AnimationSpec windowAnimationSpec = - createAnimationSpec(animation); - - return new FadeAnimationAdapter( - windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, windowToken); + protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec, + boolean show, WindowToken windowToken) { + return new FadeAnimationAdapter(animationSpec, windowToken.getSurfaceAnimationRunner(), + show, windowToken); } protected LocalAnimationAdapter.AnimationSpec createAnimationSpec( @@ -140,7 +137,7 @@ public class FadeAnimationController { protected class FadeAnimationAdapter extends LocalAnimationAdapter { protected final boolean mShow; - private final WindowToken mToken; + protected final WindowToken mToken; FadeAnimationAdapter(AnimationSpec windowAnimationSpec, SurfaceAnimationRunner surfaceAnimationRunner, boolean show, diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java index 52a7ac75e2dcd..cf36c85ebabf7 100644 --- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java +++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java @@ -18,6 +18,10 @@ package com.android.server.wm; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM; +import android.os.HandlerExecutor; +import android.util.ArrayMap; +import android.view.SurfaceControl; +import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -33,10 +37,11 @@ import java.util.ArrayList; */ public class FadeRotationAnimationController extends FadeAnimationController { - private final ArrayList mTargetWindowTokens = new ArrayList<>(); + /** The map of window token to its animation leash. */ + private final ArrayMap mTargetWindowTokens = new ArrayMap<>(); private final WindowManagerService mService; /** If non-null, it usually indicates that there will be a screen rotation animation. */ - private final Runnable mFrozenTimeoutRunnable; + private final Runnable mTimeoutRunnable; private final WindowToken mNavBarToken; /** A runnable which gets called when the {@link #show()} is called. */ @@ -45,16 +50,30 @@ public class FadeRotationAnimationController extends FadeAnimationController { /** Whether to use constant zero alpha animation. */ private boolean mHideImmediately; + /** Whether this controller is triggered from shell transition. */ + private final boolean mIsChangeTransition; + + /** Whether the start transaction of the transition is committed (by shell). */ + private boolean mIsStartTransactionCommitted; + + /** The list to store the drawn tokens before the rotation animation starts. */ + private ArrayList mPendingShowTokens; + public FadeRotationAnimationController(DisplayContent displayContent) { super(displayContent); mService = displayContent.mWmService; - mFrozenTimeoutRunnable = mService.mDisplayFrozen ? () -> { + mIsChangeTransition = displayContent.inTransition() + && displayContent.mTransitionController.getCollectingTransitionType() + == WindowManager.TRANSIT_CHANGE; + mIsStartTransactionCommitted = !mIsChangeTransition; + mTimeoutRunnable = displayContent.getRotationAnimation() != null + || mIsChangeTransition ? () -> { synchronized (mService.mGlobalLock) { displayContent.finishFadeRotationAnimationIfPossible(); mService.mWindowPlacerLocked.performSurfacePlacement(); } } : null; - if (mFrozenTimeoutRunnable != null) { + if (mTimeoutRunnable != null) { // Hide the windows immediately because screen should have been covered by screenshot. mHideImmediately = true; } @@ -68,7 +87,7 @@ public class FadeRotationAnimationController extends FadeAnimationController { // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation // bar is currently controlled by recents animation. if (!displayPolicy.navigationBarCanMove() && !navBarControlledByRecents) { - mTargetWindowTokens.add(mNavBarToken); + mTargetWindowTokens.put(mNavBarToken, null); } } else { mNavBarToken = null; @@ -79,7 +98,7 @@ public class FadeRotationAnimationController extends FadeAnimationController { if (w.mActivityRecord == null && w.mHasSurface && !w.mForceSeamlesslyRotate && !w.mIsWallpaper && !w.mIsImWindow && w != navigationBar && w != notificationShade) { - mTargetWindowTokens.add(w.mToken); + mTargetWindowTokens.put(w.mToken, null); } }, true /* traverseTopToBottom */); } @@ -87,12 +106,13 @@ public class FadeRotationAnimationController extends FadeAnimationController { /** Applies show animation on the previously hidden window tokens. */ void show() { for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { - final WindowToken windowToken = mTargetWindowTokens.get(i); + final WindowToken windowToken = mTargetWindowTokens.keyAt(i); fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM); } mTargetWindowTokens.clear(); - if (mFrozenTimeoutRunnable != null) { - mService.mH.removeCallbacks(mFrozenTimeoutRunnable); + mPendingShowTokens = null; + if (mTimeoutRunnable != null) { + mService.mH.removeCallbacks(mTimeoutRunnable); } if (mOnShowRunnable != null) { mOnShowRunnable.run(); @@ -105,10 +125,22 @@ public class FadeRotationAnimationController extends FadeAnimationController { * controller is created for normal rotation. */ boolean show(WindowToken token) { - if (mFrozenTimeoutRunnable != null && mTargetWindowTokens.remove(token)) { + 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); + } + return false; + } + if (mTimeoutRunnable != null && mTargetWindowTokens.remove(token) != null) { fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM); if (mTargetWindowTokens.isEmpty()) { - mService.mH.removeCallbacks(mFrozenTimeoutRunnable); + mService.mH.removeCallbacks(mTimeoutRunnable); return true; } } @@ -118,11 +150,11 @@ public class FadeRotationAnimationController extends FadeAnimationController { /** Applies hide animation on the window tokens which may be seamlessly rotated later. */ void hide() { for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { - final WindowToken windowToken = mTargetWindowTokens.get(i); + final WindowToken windowToken = mTargetWindowTokens.keyAt(i); fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM); } - if (mFrozenTimeoutRunnable != null) { - mService.mH.postDelayed(mFrozenTimeoutRunnable, + if (mTimeoutRunnable != null) { + mService.mH.postDelayed(mTimeoutRunnable, WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION); } } @@ -131,7 +163,6 @@ public class FadeRotationAnimationController extends FadeAnimationController { void hideImmediately(WindowToken windowToken) { final boolean original = mHideImmediately; mHideImmediately = true; - mTargetWindowTokens.add(windowToken); fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM); mHideImmediately = original; } @@ -143,16 +174,43 @@ public class FadeRotationAnimationController extends FadeAnimationController { /** Returns {@code true} if the controller will run fade animations on the window. */ boolean isTargetToken(WindowToken token) { - return mTargetWindowTokens.contains(token); + return mTargetWindowTokens.containsKey(token); } void setOnShowRunnable(Runnable onShowRunnable) { mOnShowRunnable = onShowRunnable; } + /** + * Puts initial operation of leash to the transaction which will be executed when the + * transition starts. And associate transaction callback to consume pending animations. + */ + void setupStartTransaction(SurfaceControl.Transaction t) { + // 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); + if (leash != null) { + t.setAlpha(leash, 0f); + } + } + // If there are windows have redrawn in new rotation but the start transaction has not + // been applied yet, the fade-in animation will be deferred. So once the transaction is + // committed, the fade-in animation can run with screen rotation animation. + t.addTransactionCommittedListener(new HandlerExecutor(mService.mH), () -> { + synchronized (mService.mGlobalLock) { + mIsStartTransactionCommitted = true; + if (mPendingShowTokens == null) return; + for (int i = mPendingShowTokens.size() - 1; i >= 0; i--) { + mDisplayContent.finishFadeRotationAnimation(mPendingShowTokens.get(i)); + } + mPendingShowTokens = null; + } + }); + } + @Override public Animation getFadeInAnimation() { - if (mFrozenTimeoutRunnable != null) { + if (mTimeoutRunnable != null) { // Use a shorter animation so it is easier to align with screen rotation animation. return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter); } @@ -162,8 +220,28 @@ public class FadeRotationAnimationController extends FadeAnimationController { @Override public Animation getFadeOutAnimation() { if (mHideImmediately) { - return new AlphaAnimation(0 /* fromAlpha */, 0 /* toAlpha */); + // For change transition, the hide transaction needs to be applied with sync transaction + // (setupStartTransaction). So keep alpha 1 just to get the animation leash. + final float alpha = mIsChangeTransition ? 1 : 0; + return new AlphaAnimation(alpha /* fromAlpha */, alpha /* toAlpha */); } return super.getFadeOutAnimation(); } + + @Override + protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec, + boolean show, WindowToken windowToken) { + return new FadeAnimationAdapter(animationSpec, windowToken.getSurfaceAnimationRunner(), + show, windowToken) { + @Override + public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, + int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + // The fade cycle is done when showing, so only need to store the leash when hiding. + if (!show) { + mTargetWindowTokens.put(mToken, animationLeash); + } + super.startAnimation(animationLeash, t, type, finishCallback); + } + }; + } } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 405a9e5637b1f..e33c4403a98b0 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -249,16 +249,6 @@ class InsetsStateController { return result; } - public void addProvidersToTransition() { - for (int i = mProviders.size() - 1; i >= 0; --i) { - final InsetsSourceProvider p = mProviders.valueAt(i); - if (p == null) continue; - final WindowContainer wc = p.mWin; - if (wc == null) continue; - mDisplayContent.mTransitionController.collect(wc); - } - } - /** * @return The provider of a specific type. */ diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java index 7abf3b820c186..af8293aab977d 100644 --- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java +++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java @@ -37,7 +37,6 @@ public class NavBarFadeAnimationController extends FadeAnimationController{ private static final Interpolator FADE_OUT_INTERPOLATOR = new PathInterpolator(0.2f, 0f, 1f, 1f); - private DisplayContent mDisplayContent; private final WindowState mNavigationBar; private Animation mFadeInAnimation; private Animation mFadeOutAnimation; @@ -47,7 +46,6 @@ public class NavBarFadeAnimationController extends FadeAnimationController{ public NavBarFadeAnimationController(DisplayContent displayContent) { super(displayContent); - mDisplayContent = displayContent; mNavigationBar = displayContent.getDisplayPolicy().getNavigationBar(); mFadeInAnimation = new AlphaAnimation(0f, 1f); mFadeInAnimation.setDuration(FADE_IN_DURATION); @@ -69,16 +67,10 @@ public class NavBarFadeAnimationController extends FadeAnimationController{ } @Override - protected FadeAnimationAdapter createAdapter(boolean show, WindowToken windowToken) { - final Animation animation = show ? getFadeInAnimation() : getFadeOutAnimation(); - if (animation == null) { - return null; - } - - final LocalAnimationAdapter.AnimationSpec windowAnimationSpec = - createAnimationSpec(animation); + protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec, + boolean show, WindowToken windowToken) { return new NavFadeAnimationAdapter( - windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, windowToken, + animationSpec, windowToken.getSurfaceAnimationRunner(), show, windowToken, show ? mFadeInParent : mFadeOutParent); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 7349594483a8a..39747472b2da5 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -596,6 +596,12 @@ 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 FadeRotationAnimationController controller = dc.getFadeRotationAnimationController(); + if (controller != null) { + controller.setupStartTransaction(transaction); + } mStartTransaction = transaction; mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); buildFinishTransaction(mFinishTransaction, info.getRootLeash()); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index e05457010df8e..99dfe137e2088 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -337,6 +337,29 @@ class TransitionController { mCollectingTransition.collectExistenceChange(wc); } + /** + * Collects the window containers which need to be synced with the changing display (e.g. + * rotating) to the given transition or the current collecting transition. + */ + void collectForDisplayChange(@NonNull DisplayContent dc, @Nullable Transition incoming) { + if (incoming == null) incoming = mCollectingTransition; + if (incoming == null) return; + final Transition transition = incoming; + // Collect all visible tasks. + dc.forAllLeafTasks(task -> { + if (task.isVisible()) { + transition.collect(task); + } + }, true /* traverseTopToBottom */); + // Collect all visible non-app windows which need to be drawn before the animation starts. + dc.forAllWindows(w -> { + if (w.mActivityRecord == null && w.isVisible() && !inTransition(w.mToken) + && dc.shouldSyncRotationChange(w)) { + transition.collect(w.mToken); + } + }, true /* traverseTopToBottom */); + } + /** @see Transition#setOverrideAnimation */ void setOverrideAnimation(TransitionInfo.AnimationOptions options, @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 0649b25e0f042..0fd730c6f1a7b 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -335,10 +335,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // Go through all tasks and collect them before the rotation // TODO(shell-transitions): move collect() to onConfigurationChange once // wallpaper handling is synchronized. - dc.forAllTasks(task -> { - if (task.isVisible()) transition.collect(task); - }); - dc.getInsetsStateController().addProvidersToTransition(); + dc.mTransitionController.collectForDisplayChange(dc, transition); dc.sendNewConfiguration(); effects |= TRANSACT_EFFECTS_LIFECYCLE; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 44edaa256283b..c9b56dbde2638 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1473,12 +1473,12 @@ class WindowState extends WindowContainer implements WindowManagerP if (changing) { mLastFreezeDuration = 0; if (mWmService.mRoot.mOrientationChangeComplete - && mDisplayContent.waitForUnfreeze(this)) { + && mDisplayContent.shouldSyncRotationChange(this)) { mWmService.mRoot.mOrientationChangeComplete = false; } } else { // The orientation change is completed. If it was hidden by the animation, reshow it. - mDisplayContent.finishFadeRotationAnimation(this); + mDisplayContent.finishFadeRotationAnimation(mToken); } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index b147455e83cda..316051e9d242a 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -566,7 +566,7 @@ class WindowStateAnimator { if (w.getOrientationChanging()) { if (!w.isDrawn()) { - if (w.mDisplayContent.waitForUnfreeze(w)) { + if (w.mDisplayContent.shouldSyncRotationChange(w)) { w.mWmService.mRoot.mOrientationChangeComplete = false; mAnimator.mLastWindowFreezeSource = w; } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 318ad06db66b2..e5a3b7a8748c2 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -693,11 +693,8 @@ class WindowToken extends WindowContainer { @Override public String toString() { if (stringName == null) { - StringBuilder sb = new StringBuilder(); - sb.append("WindowToken{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" "); sb.append(token); sb.append('}'); - stringName = sb.toString(); + stringName = "WindowToken{" + Integer.toHexString(System.identityHashCode(this)) + + " type=" + windowType + " " + token + "}"; } return stringName; } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index d7a0ab3afbc2f..dc0e02800bb29 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1336,7 +1336,6 @@ public class DisplayContentTests extends WindowTestsBase { spyOn(rotationAnim); // Assume that the display rotation is changed so it is frozen in preparation for animation. doReturn(true).when(rotationAnim).hasScreenshot(); - mWm.mDisplayFrozen = true; displayContent.getDisplayRotation().setRotation((displayContent.getRotation() + 1) % 4); displayContent.setRotationAnimation(rotationAnim); // The fade rotation animation also starts to hide some non-app windows. @@ -1347,9 +1346,9 @@ public class DisplayContentTests extends WindowTestsBase { w.setOrientationChanging(true); } // The display only waits for the app window to unfreeze. - assertFalse(displayContent.waitForUnfreeze(statusBar)); - assertFalse(displayContent.waitForUnfreeze(navBar)); - assertTrue(displayContent.waitForUnfreeze(app)); + assertFalse(displayContent.shouldSyncRotationChange(statusBar)); + assertFalse(displayContent.shouldSyncRotationChange(navBar)); + assertTrue(displayContent.shouldSyncRotationChange(app)); // If all windows animated by fade rotation animation have done the orientation change, // the animation controller should be cleared. statusBar.setOrientationChanging(false); 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 d9a166a626737..b7417c4e8dd0c 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,9 @@ 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.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; @@ -32,7 +35,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -44,6 +49,7 @@ import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.ArraySet; import android.view.SurfaceControl; +import android.view.TransactionCommittedListener; import android.window.ITaskOrganizer; import android.window.ITransitionPlayer; import android.window.TransitionInfo; @@ -52,6 +58,7 @@ import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -468,6 +475,54 @@ public class TransitionTests extends WindowTestsBase { assertTrue(awaitInWmLock(() -> latch.await(3, TimeUnit.SECONDS))); } + @Test + public void testDisplayRotationChange() { + final Task task = createActivityRecord(mDisplayContent).getTask(); + final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar"); + final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar"); + final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime"); + final WindowState[] windows = { statusBar, navBar, ime }; + makeWindowVisible(windows); + mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs); + mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs); + final TestTransitionPlayer player = registerTestTransitionPlayer(); + + mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1); + mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */); + final FadeRotationAnimationController fadeController = + mDisplayContent.getFadeRotationAnimationController(); + assertNotNull(fadeController); + for (WindowState w : windows) { + w.setOrientationChanging(true); + } + player.startTransition(); + + assertFalse(statusBar.mToken.inTransition()); + assertTrue(ime.mToken.inTransition()); + assertTrue(task.inTransition()); + + // Status bar finishes drawing before the start transaction. Its fade-in animation will be + // executed until the transaction is committed, so it is still in target tokens. + statusBar.setOrientationChanging(false); + assertTrue(fadeController.isTargetToken(statusBar.mToken)); + + final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class); + final ArgumentCaptor listenerCaptor = + ArgumentCaptor.forClass(TransactionCommittedListener.class); + player.onTransactionReady(startTransaction); + + verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture()); + // The transaction is committed, so fade-in animation for status bar is consumed. + listenerCaptor.getValue().onTransactionCommitted(); + assertFalse(fadeController.isTargetToken(statusBar.mToken)); + + // Status bar finishes drawing after the start transaction, so its fade-in animation can + // execute directly. + navBar.setOrientationChanging(false); + assertFalse(fadeController.isTargetToken(navBar.mToken)); + assertNull(mDisplayContent.getFadeRotationAnimationController()); + } + @Test public void testIntermediateVisibility() { final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 92fd682960069..a985de50b9aa4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -1660,10 +1660,17 @@ class WindowTestsBase extends SystemServiceTestsBase { mLastRequest = request; } - public void start() { + void startTransition() { mOrganizer.startTransition(mLastRequest.getType(), mLastTransit, null); - mLastTransit.onTransactionReady(mLastTransit.getSyncId(), - mock(SurfaceControl.Transaction.class)); + } + + void onTransactionReady(SurfaceControl.Transaction t) { + mLastTransit.onTransactionReady(mLastTransit.getSyncId(), t); + } + + void start() { + startTransition(); + onTransactionReady(mock(SurfaceControl.Transaction.class)); } public void finish() {