From 5a5734bbbaa96e4c2647eaed655ffac128ea3d5f Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Sat, 25 Jul 2020 00:37:48 +0800 Subject: [PATCH 1/2] Finish fixed rotation after recents moved to top While the task transition is running, its activity is the animation source. If recents animation is triggered and finished before the transition is done, the animation source will be notified that the transition is finished. At this timing, the rotated activity is still the top activity. Since it is moving to back, the display orientation is unnecessary to be updated for the intermediate state. That avoids flickering by not committing visibility due to configuration change. Fixes: 160239226 Test: atest RecentsAnimationControllerTest# \ testKeepFixedRotationWhenMovingRecentsToTop Change-Id: I5c5c12338b9bc0cd8a99655a843a33d8729f8892 Merged-In: I5c5c12338b9bc0cd8a99655a843a33d8729f8892 --- .../com/android/server/wm/DisplayContent.java | 21 ++++++++++++-- .../server/wm/RecentsAnimationController.java | 8 +++-- .../server/wm/DisplayContentTests.java | 2 +- .../wm/RecentsAnimationControllerTest.java | 29 +++++++++++++++++-- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index fcbc7564cabbd..ed1f221576d9c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5642,6 +5642,9 @@ class DisplayContent extends WindowContainer= 0; i--) { final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i); if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) { @@ -742,8 +747,7 @@ public class RecentsAnimationController implements DeathRecipient { mTargetActivityRecord.token); } } - mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation( - reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION /* moveRecentsToBack */); + mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation(); // Notify that the animation has ended if (mStatusBar != null) { 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 cbccfb3725e9b..dc838f1bb2889 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1282,7 +1282,7 @@ public class DisplayContentTests extends WindowTestsBase { assertFalse(displayRotation.updateRotationUnchecked(false)); // Rotation can be updated if the recents animation is finished. - mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation(false); + mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation(); assertTrue(displayRotation.updateRotationUnchecked(false)); // Rotation can be updated if the recents animation is animating but it is not on top, e.g. diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 8e85e7b96d1f0..f2771175b5237 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -363,12 +363,14 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { assertFalse(homeActivity.hasFixedRotationTransform()); } - @Test - public void testClearFixedRotationLaunchingAppAfterCleanupAnimation() { + private ActivityRecord prepareFixedRotationLaunchingAppWithRecentsAnim() { final ActivityRecord homeActivity = createHomeActivity(); homeActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); final ActivityRecord activity = createActivityRecord(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + // Add a window so it can be animated by the recents. + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + activity.addWindow(win); // Assume an activity is launching to different rotation. mDefaultDisplay.setFixedRotationLaunchingApp(activity, (mDefaultDisplay.getRotation() + 1) % 4); @@ -379,6 +381,14 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { // Before the transition is done, the recents animation is triggered. initializeRecentsAnimationController(mController, homeActivity); assertFalse(homeActivity.hasFixedRotationTransform()); + assertTrue(mController.isAnimatingTask(activity.getTask())); + + return activity; + } + + @Test + public void testClearFixedRotationLaunchingAppAfterCleanupAnimation() { + final ActivityRecord activity = prepareFixedRotationLaunchingAppWithRecentsAnim(); // Simulate giving up the swipe up gesture to keep the original activity as top. mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); @@ -387,6 +397,21 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { assertFalse(mDefaultDisplay.hasTopFixedRotationLaunchingApp()); } + @Test + public void testKeepFixedRotationWhenMovingRecentsToTop() { + final ActivityRecord activity = prepareFixedRotationLaunchingAppWithRecentsAnim(); + // Assume a transition animation has started running before recents animation. Then the + // activity will receive onAnimationFinished that notifies app transition finished when + // removing the recents animation of task. + activity.getTask().getAnimationSources().add(activity); + + // Simulate swiping to home/recents before the transition is done. + mController.cleanupAnimation(REORDER_MOVE_TO_TOP); + // The rotation transform should be preserved. In real case, it will be cleared by the next + // move-to-top transition. + assertTrue(activity.hasFixedRotationTransform()); + } + @Test public void testWallpaperHasFixedRotationApplied() { mWm.setRecentsAnimationController(mController); From 1f27ebfa55605dcdbda2e511d5829bdb6f686924 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Sat, 25 Jul 2020 01:44:51 +0800 Subject: [PATCH 2/2] Allow to replace fixed rotation state Assume there are 2 activities with different fixed rotation states A and B. And wallpaper was associated with A. When the wallpaper target is changed to B, wallpaper should be able to change the association to B so their rotation transform can be updated at the same time. This also avoids that if the previous associated token is somehow inactive, wallpaper won't in a dangling rotated state. Also add a check of updating rotated launching app for the case: launching a portrait app from a landscape app and trigger recents animation immediately before the animation of portrait app is done. And then finish recents animation by keeping the portrait app as the top activity. The expected result should be that the display is rotated from landscape to portrait seamlessly. But if the recents activity is set to the rotated launching app, the display rotation will be unable to update because when receiving animation done of the portrait app, the recents animation is still active that skips the rotation change. Then the portrait app will be updated to landscape temporally. Fixes: 161056612 Test: atest WindowTokenTests#testFinishFixedRotationTransform Change-Id: Ic134c4326db836e35385f290f996f0d841da693d Merged-In: Ic134c4326db836e35385f290f996f0d841da693d --- .../com/android/server/wm/DisplayContent.java | 7 ++++++- .../com/android/server/wm/WindowToken.java | 20 ++++++++++++------- .../android/server/wm/WindowTokenTests.java | 18 ++++++++++++++--- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ed1f221576d9c..0363ea0e75124 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1573,7 +1573,12 @@ class DisplayContent extends WindowContainer { void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames, Configuration config) { if (mFixedRotationTransformState != null) { - return; + cleanUpFixedRotationTransformState(true /* replacing */); } mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames, new Configuration(config), mDisplayContent.getRotation()); @@ -548,12 +548,12 @@ class WindowToken extends WindowContainer { * one. This takes the same effect as {@link #applyFixedRotationTransform}. */ void linkFixedRotationTransform(WindowToken other) { - if (mFixedRotationTransformState != null) { + final FixedRotationTransformState fixedRotationState = other.mFixedRotationTransformState; + if (fixedRotationState == null || mFixedRotationTransformState == fixedRotationState) { return; } - final FixedRotationTransformState fixedRotationState = other.mFixedRotationTransformState; - if (fixedRotationState == null) { - return; + if (mFixedRotationTransformState != null) { + cleanUpFixedRotationTransformState(true /* replacing */); } mFixedRotationTransformState = fixedRotationState; fixedRotationState.mAssociatedTokens.add(this); @@ -609,11 +609,17 @@ class WindowToken extends WindowContainer { // The state is cleared at the end, because it is used to indicate that other windows can // use seamless rotation when applying rotation to display. for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) { - state.mAssociatedTokens.get(i).cleanUpFixedRotationTransformState(); + state.mAssociatedTokens.get(i).cleanUpFixedRotationTransformState( + false /* replacing */); } } - private void cleanUpFixedRotationTransformState() { + private void cleanUpFixedRotationTransformState(boolean replacing) { + if (replacing && mFixedRotationTransformState.mAssociatedTokens.size() > 1) { + // The state is not only used by self. Make sure to leave the influence by others. + mFixedRotationTransformState.mAssociatedTokens.remove(this); + mFixedRotationTransformState.mRotatedContainers.remove(this); + } mFixedRotationTransformState = null; notifyFixedRotationTransform(false /* enabled */); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java index 23a097eb0c7ce..0896db4b55327 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java @@ -24,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -139,6 +140,8 @@ public class WindowTokenTests extends WindowTestsBase { public void testFinishFixedRotationTransform() { final WindowToken appToken = mAppWindow.mToken; final WindowToken wallpaperToken = mWallpaperWindow.mToken; + final WindowToken testToken = + WindowTestUtils.createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent); final Configuration config = new Configuration(mDisplayContent.getConfiguration()); final int originalRotation = config.windowConfiguration.getRotation(); final int targetRotation = (originalRotation + 1) % 4; @@ -151,11 +154,20 @@ public class WindowTokenTests extends WindowTestsBase { assertEquals(targetRotation, appToken.getWindowConfiguration().getRotation()); assertEquals(targetRotation, wallpaperToken.getWindowConfiguration().getRotation()); - // The display doesn't rotate, the transformation will be canceled. - mAppWindow.mToken.finishFixedRotationTransform(); + testToken.applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config); + // The wallpaperToken was linked to appToken, this should make it link to testToken. + wallpaperToken.linkFixedRotationTransform(testToken); - // The window tokens should restore to the original rotation. + // Assume the display doesn't rotate, the transformation will be canceled. + appToken.finishFixedRotationTransform(); + + // The appToken should restore to the original rotation. assertEquals(originalRotation, appToken.getWindowConfiguration().getRotation()); + // The wallpaperToken is linked to testToken, it should keep the target rotation. + assertNotEquals(originalRotation, wallpaperToken.getWindowConfiguration().getRotation()); + + testToken.finishFixedRotationTransform(); + // The rotation of wallpaperToken should be restored because its linked state is finished. assertEquals(originalRotation, wallpaperToken.getWindowConfiguration().getRotation()); }