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
This commit is contained in:
Evan Rosky
2022-12-12 23:22:35 +00:00
parent 0f0b247bdf
commit 9fd4852818
7 changed files with 127 additions and 3 deletions

View File

@@ -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");
}

View File

@@ -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);

View File

@@ -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<Transitions> {
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) {

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> 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<WindowContainer> 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);