Suppress app transition while recents is running

This fixes conflict between app transition and recents animation in the
following scenario.

1) App transition animation finishes after app closing animation, which
is controlled by recents, finishes.

During the app closing animation, recents makes the closing app surface
invisible, but app transition animation overrides it to visible again.
This causes a flicker.

2) App transition starts during recents animation.

This can happen when a user launches an activity, and immediately after
that, swipes up the screen to close the app.

While recents is running, we assume animation on tasks is controlled by
recents, and visibility is commited without animation after recents
animation finishes. However starting app transition during recents
breaks this assumption, which ends up with playing one more unexpected
closing animation (so users see closing animation twice).

Bug: 223499269
Bug: 231669960
Test: atest AppTransitionTest + manual tests
  Test 1
   1. Launch Gmail app
   2. Click icon on the bottom tab (e.g. Chat)
   3. Swipe up from the bottom (immediately after step 2)
   4. Verify closing animation only plays once
  Test 2
   1. Launch "Google TV"
   2. Play a trailer
   3. Full screen and PIP mode switch twice
   4. Verify PIP window is shown
  Test 3
   1. Change phone to portlait mode
   2. Launch Photo app
   3. Swipe up from the bottom
   4. Verify no rotation animation on the launcher
Change-Id: Ic18d00812308903db08d3564136f33f1eccf408c
This commit is contained in:
Issei Suzuki
2022-04-27 15:37:03 +00:00
parent 9c9916e4d9
commit d49b5d442d
7 changed files with 74 additions and 5 deletions

View File

@@ -2485,6 +2485,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
"323235828": {
"message": "Delaying app transition for recents animation to finish",
"level": "VERBOSE",
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
"327461496": {
"message": "Complete pause: %s",
"level": "VERBOSE",

View File

@@ -9338,6 +9338,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
sb.append(mUserId);
sb.append(' ');
sb.append(intent.getComponent().flattenToShortString());
sb.append("}");
stringName = sb.toString();
return stringName;
}

View File

@@ -68,6 +68,7 @@ import static com.android.server.wm.AppTransition.isNormalTransit;
import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -1142,13 +1143,17 @@ public class AppTransitionController {
if (activity == null) {
continue;
}
if (activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Delaying app transition for recents animation to finish");
return false;
}
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Check opening app=%s: allDrawn=%b startingDisplayed=%b "
+ "startingMoved=%b isRelaunching()=%b startingWindow=%s",
activity, activity.allDrawn, activity.startingDisplayed,
activity.startingMoved, activity.isRelaunching(),
activity.mStartingWindow);
final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
return false;

View File

@@ -5265,7 +5265,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
void prepareAppTransition(@WindowManager.TransitionType int transit,
@WindowManager.TransitionFlags int flags) {
final boolean prepared = mAppTransition.prepareAppTransition(transit, flags);
if (prepared && okToAnimate()) {
if (prepared && okToAnimate() && transit != TRANSIT_NONE) {
mSkipAppTransitionAnimation = false;
}
}

View File

@@ -1584,6 +1584,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return true;
}
void forAllWindowContainers(Consumer<WindowContainer> callback) {
callback.accept(this);
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
mChildren.get(i).forAllWindowContainers(callback);
}
}
/**
* For all windows at or below this container call the callback.
* @param callback Calls the {@link ToBooleanFunction#apply} method for each window found and

View File

@@ -118,6 +118,7 @@ import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
@@ -3052,13 +3053,22 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
if (mRecentsAnimationController != null) {
final RecentsAnimationController controller = mRecentsAnimationController;
mRecentsAnimationController = null;
controller.cleanupAnimation(reorderMode);
// TODO(mult-display): currently only default display support recents animation.
getDefaultDisplayContentLocked().mAppTransition.updateBooster();
// TODO(multi-display): currently only default display support recents animation.
final DisplayContent dc = getDefaultDisplayContentLocked();
if (dc.mAppTransition.isTransitionSet()) {
dc.mSkipAppTransitionAnimation = true;
}
dc.forAllWindowContainers((wc) -> {
if (wc.isAnimating(TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) {
wc.cancelAnimation();
}
});
}
}

View File

@@ -39,8 +39,10 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
@@ -48,7 +50,9 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import android.graphics.Rect;
import android.os.Binder;
@@ -376,7 +380,7 @@ public class AppTransitionTests extends WindowTestsBase {
doReturn(false).when(dc).onDescendantOrientationChanged(any());
final WindowState exitingAppWindow = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
dc, "exiting app");
final ActivityRecord exitingActivity= exitingAppWindow.mActivityRecord;
final ActivityRecord exitingActivity = exitingAppWindow.mActivityRecord;
// Wait until everything in animation handler get executed to prevent the exiting window
// from being removed during WindowSurfacePlacer Traversal.
waitUntilHandlersIdle();
@@ -404,6 +408,41 @@ public class AppTransitionTests extends WindowTestsBase {
assertTrue(runner.mCancelled);
}
@Test
public void testDelayWhileRecents() {
final DisplayContent dc = createNewDisplay(Display.STATE_ON);
doReturn(false).when(dc).onDescendantOrientationChanged(any());
final Task task = createTask(dc);
// Simulate activity1 launches activity2.
final ActivityRecord activity1 = createActivityRecord(task);
activity1.setVisible(true);
activity1.mVisibleRequested = false;
activity1.allDrawn = true;
final ActivityRecord activity2 = createActivityRecord(task);
activity2.setVisible(false);
activity2.mVisibleRequested = true;
activity2.allDrawn = true;
dc.mClosingApps.add(activity1);
dc.mOpeningApps.add(activity2);
dc.prepareAppTransition(TRANSIT_OPEN);
assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
// Wait until everything in animation handler get executed to prevent the exiting window
// from being removed during WindowSurfacePlacer Traversal.
waitUntilHandlersIdle();
// Start recents
doReturn(true).when(task)
.isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
dc.mAppTransitionController.handleAppTransitionReady();
verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
}
@Test
public void testGetAnimationStyleResId() {
// Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without