diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 3b4a31b6cb790..2ad7599602f85 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1585,8 +1585,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(); } /** @@ -1876,16 +1877,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; @@ -3230,6 +3231,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 c1333e58ea13e..525d84be86800 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 bae5465c4fa29..0b9174210a19e 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() {