From 9fd48528183de7fb6d7df28b6ce04ea6a45d7372 Mon Sep 17 00:00:00 2001 From: Evan Rosky Date: Mon, 12 Dec 2022 23:22:35 +0000 Subject: [PATCH] Add support for the no-animation intent option Port this over from legacy transitions. The heuristic is slightly different since shell and legacy work differently but it should be pretty close: basically if the no-animation is the only non-going-invisible participant, then we'll skip the animation. Bug: 262314590 Test: atest TransitionTests#testCreateInfo_NoAnimation Change-Id: I5d5bc5b83417c20f03358160326ba18e3dd7f461 --- core/java/android/window/TransitionInfo.java | 9 +++- .../transition/DefaultTransitionHandler.java | 8 ++++ .../wm/shell/transition/Transitions.java | 29 ++++++++++++ .../core/java/com/android/server/wm/Task.java | 2 +- .../com/android/server/wm/Transition.java | 30 +++++++++++- .../server/wm/TransitionController.java | 5 ++ .../android/server/wm/TransitionTests.java | 47 +++++++++++++++++++ 7 files changed, 127 insertions(+), 3 deletions(-) diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index a35e13e3ac3be..c8a69e274e819 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -141,8 +141,11 @@ public final class TransitionInfo implements Parcelable { /** The window was animated by back gesture. */ public static final int FLAG_BACK_GESTURE_ANIMATED = 1 << 17; + /** The window should have no animation (by policy). */ + public static final int FLAG_NO_ANIMATION = 1 << 18; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ - public static final int FLAG_FIRST_CUSTOM = 1 << 18; + public static final int FLAG_FIRST_CUSTOM = 1 << 19; /** The change belongs to a window that won't contain activities. */ public static final int FLAGS_IS_NON_APP_WINDOW = @@ -169,6 +172,7 @@ public final class TransitionInfo implements Parcelable { FLAG_IS_OCCLUDED, FLAG_IS_SYSTEM_WINDOW, FLAG_BACK_GESTURE_ANIMATED, + FLAG_NO_ANIMATION, FLAG_FIRST_CUSTOM }) public @interface ChangeFlags {} @@ -387,6 +391,9 @@ public final class TransitionInfo implements Parcelable { if ((flags & FLAG_BACK_GESTURE_ANIMATED) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("FLAG_BACK_GESTURE_ANIMATED"); } + if ((flags & FLAG_NO_ANIMATION) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("NO_ANIMATION"); + } if ((flags & FLAG_FIRST_CUSTOM) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM"); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 63d4a6f5acd91..618c4465db3ba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -301,6 +301,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return true; } + // check if no-animation and skip animation if so. + if (Transitions.isAllNoAnimation(info)) { + startTransaction.apply(); + finishTransaction.apply(); + finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + return true; + } + if (mAnimations.containsKey(transition)) { throw new IllegalStateException("Got a duplicate startAnimation call for " + transition); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index c6935c0544221..039f0e3b59174 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -26,6 +26,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; +import static android.window.TransitionInfo.FLAG_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; @@ -448,6 +449,34 @@ public class Transitions implements RemoteCallable { return -1; } + /** + * Look through a transition and see if all non-closing changes are no-animation. If so, no + * animation should play. + */ + static boolean isAllNoAnimation(TransitionInfo info) { + if (isClosingType(info.getType())) { + // no-animation is only relevant for launching (open) activities. + return false; + } + boolean hasNoAnimation = false; + final int changeSize = info.getChanges().size(); + for (int i = changeSize - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (isClosingType(change.getMode())) { + // ignore closing apps since they are a side-effect of the transition and don't + // animate. + continue; + } + if (change.hasFlags(FLAG_NO_ANIMATION)) { + hasNoAnimation = true; + } else { + // at-least one relevant participant *is* animated, so we need to animate. + return false; + } + } + return hasNoAnimation; + } + @VisibleForTesting void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 07e3b836bd1de..d26b22464cbaf 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -5029,10 +5029,10 @@ class Task extends TaskFragment { final DisplayContent dc = mDisplayContent; if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: starting " + r); - // TODO(shell-transitions): record NO_ANIMATION flag somewhere. if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { dc.prepareAppTransition(TRANSIT_NONE); mTaskSupervisor.mNoAnimActivities.add(r); + mTransitionController.setNoAnimation(r); } else { dc.prepareAppTransition(TRANSIT_OPEN); mTaskSupervisor.mNoAnimActivities.remove(r); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index b7f3cb4d800d7..0c20d034769dc 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -47,6 +47,7 @@ import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD; import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; +import static android.window.TransitionInfo.FLAG_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; @@ -916,6 +917,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return mRemoteTransition; } + void setNoAnimation(WindowContainer wc) { + final ChangeInfo change = mChanges.get(wc); + if (change == null) { + throw new IllegalStateException("Can't set no-animation property of non-participant"); + } + change.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION; + } + @Override public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) { if (syncId != mSyncId) { @@ -1442,6 +1451,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { i++; targets.add(parent); } + if ((changes.get(target).mFlags & ChangeInfo.FLAG_CHANGE_NO_ANIMATION) != 0) { + changes.get(parent).mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION; + } else { + changes.get(parent).mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION; + } } } @@ -1880,11 +1894,21 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { private static final int FLAG_TRANSIENT_LAUNCH = 2; private static final int FLAG_ABOVE_TRANSIENT_LAUNCH = 4; + /** This container explicitly requested no-animation (usually Activity level). */ + private static final int FLAG_CHANGE_NO_ANIMATION = 0x8; + /** + * This container has at-least one child which IS animating (not marked NO_ANIMATION). + * Used during promotion. This trumps `FLAG_NO_ANIMATION` (if both are set). + */ + private static final int FLAG_CHANGE_YES_ANIMATION = 0x10; + @IntDef(prefix = { "FLAG_" }, value = { FLAG_NONE, FLAG_SEAMLESS_ROTATION, FLAG_TRANSIENT_LAUNCH, - FLAG_ABOVE_TRANSIENT_LAUNCH + FLAG_ABOVE_TRANSIENT_LAUNCH, + FLAG_CHANGE_NO_ANIMATION, + FLAG_CHANGE_YES_ANIMATION }) @Retention(RetentionPolicy.SOURCE) @interface Flag {} @@ -2055,6 +2079,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (occludesKeyguard(wc)) { flags |= FLAG_OCCLUDES_KEYGUARD; } + if ((mFlags & FLAG_CHANGE_NO_ANIMATION) != 0 + && (mFlags & FLAG_CHANGE_YES_ANIMATION) == 0) { + flags |= FLAG_NO_ANIMATION; + } return flags; } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index cf541fcde459b..4c34912165a25 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -561,6 +561,11 @@ class TransitionController { mCollectingTransition.setOverrideAnimation(options, startCallback, finishCallback); } + void setNoAnimation(WindowContainer wc) { + if (mCollectingTransition == null) return; + mCollectingTransition.setNoAnimation(wc); + } + /** @see Transition#setReady */ void setReady(WindowContainer wc, boolean ready) { if (mCollectingTransition == null) return; 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 aaf07b9c1ede8..a95b8118c2ef9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -462,6 +462,53 @@ public class TransitionTests extends WindowTestsBase { assertTrue(targets.contains(topTask)); } + @Test + public void testCreateInfo_NoAnimation() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + ArrayMap changes = transition.mChanges; + ArraySet participants = transition.mParticipants; + + final Task newTask = createTask(mDisplayContent); + final Task oldTask = createTask(mDisplayContent); + final ActivityRecord closing = createActivityRecord(oldTask); + final ActivityRecord opening = createActivityRecord(newTask); + // Start states. + changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); + changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); + transition.setNoAnimation(opening); + fillChangeMap(changes, newTask); + // End states. + closing.setVisibleRequested(false); + opening.setVisibleRequested(true); + + final int transit = transition.mType; + int flags = 0; + + // Check that no-animation flag is promoted + participants.add(oldTask); + participants.add(newTask); + participants.add(opening); + participants.add(closing); + ArrayList targets = Transition.calculateTargets(participants, changes); + TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes, + mMockT); + assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); + assertTrue(info.getChange(newTask.mRemoteToken.toWindowContainerToken()) + .hasFlags(TransitionInfo.FLAG_NO_ANIMATION)); + + // Check that no-animation flag is NOT promoted if at-least on child *is* animated + final ActivityRecord opening2 = createActivityRecord(newTask); + changes.put(opening2, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + participants.add(opening2); + targets = Transition.calculateTargets(participants, changes); + info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT); + assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); + assertFalse(info.getChange(newTask.mRemoteToken.toWindowContainerToken()) + .hasFlags(TransitionInfo.FLAG_NO_ANIMATION)); + } + @Test public void testTargets_noIntermediatesToWallpaper() { final Transition transition = createTestTransition(TRANSIT_OPEN);