Add to-top reporting to transitions
This will include a CHANGE info for tasks which have
moved to top while still visible. This allows recents to
be reported when a translucent task is running and also
provides a hook for multi-window order changes.
This also recalculates back-tasks on transient-launch finish
since, otherwise, the behind activity isn't paused. This
was because it wasn't changing visibility and just re-ordering
doesn't recalculate lifecycles.
Bug: 274696524
Test: TransitionTests#testMoveToTopWhileVisible
Start a translucent task, enter recents, then restore the task.
Change-Id: If21d076eed4db88139ffc8a7c4c018c2ef5aad93
This commit is contained in:
@@ -149,8 +149,11 @@ public final class TransitionInfo implements Parcelable {
|
||||
/** The task is launching behind home. */
|
||||
public static final int FLAG_TASK_LAUNCHING_BEHIND = 1 << 19;
|
||||
|
||||
/** The task became the top-most task even if it didn't change visibility. */
|
||||
public static final int FLAG_MOVED_TO_TOP = 1 << 20;
|
||||
|
||||
/** 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 << 20;
|
||||
public static final int FLAG_FIRST_CUSTOM = 1 << 21;
|
||||
|
||||
/** The change belongs to a window that won't contain activities. */
|
||||
public static final int FLAGS_IS_NON_APP_WINDOW =
|
||||
@@ -179,6 +182,7 @@ public final class TransitionInfo implements Parcelable {
|
||||
FLAG_BACK_GESTURE_ANIMATED,
|
||||
FLAG_NO_ANIMATION,
|
||||
FLAG_TASK_LAUNCHING_BEHIND,
|
||||
FLAG_MOVED_TO_TOP,
|
||||
FLAG_FIRST_CUSTOM
|
||||
})
|
||||
public @interface ChangeFlags {}
|
||||
|
||||
@@ -456,7 +456,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
|
||||
cancel(mWillFinishToHome);
|
||||
return;
|
||||
}
|
||||
hasChangingApp = true;
|
||||
// Don't consider order-only changes as changing apps.
|
||||
if (!TransitionUtil.isOrderOnly(change)) {
|
||||
hasChangingApp = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasChangingApp && foundRecentsClosing) {
|
||||
@@ -484,13 +487,14 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
|
||||
}
|
||||
boolean didMergeThings = false;
|
||||
if (closingTasks != null) {
|
||||
// Cancelling a task-switch. Move the tasks back to mPausing from mOpening
|
||||
// Potentially cancelling a task-switch. Move the tasks back to mPausing if they
|
||||
// are in mOpening.
|
||||
for (int i = 0; i < closingTasks.size(); ++i) {
|
||||
final TransitionInfo.Change change = closingTasks.get(i);
|
||||
int openingIdx = TaskState.indexOf(mOpeningTasks, change);
|
||||
if (openingIdx < 0) {
|
||||
Slog.e(TAG, "Back to existing recents animation from an unrecognized "
|
||||
+ "task: " + change.getTaskInfo().taskId);
|
||||
Slog.w(TAG, "Closing a task that wasn't opening, this may be split or"
|
||||
+ " something unexpected: " + change.getTaskInfo().taskId);
|
||||
continue;
|
||||
}
|
||||
mPausingTasks.add(mOpeningTasks.remove(openingIdx));
|
||||
|
||||
@@ -301,8 +301,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if no-animation and skip animation if so.
|
||||
if (Transitions.isAllNoAnimation(info)) {
|
||||
// Early check if the transition doesn't warrant an animation.
|
||||
if (Transitions.isAllNoAnimation(info) || Transitions.isAllOrderOnly(info)) {
|
||||
startTransaction.apply();
|
||||
finishTransaction.apply();
|
||||
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
|
||||
|
||||
@@ -512,6 +512,16 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
return hasNoAnimation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all changes in this transition are only ordering changes. If so, we won't animate.
|
||||
*/
|
||||
static boolean isAllOrderOnly(TransitionInfo info) {
|
||||
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
|
||||
if (!TransitionUtil.isOrderOnly(info.getChanges().get(i))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
|
||||
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
|
||||
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
|
||||
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
|
||||
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
|
||||
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
|
||||
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
|
||||
|
||||
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
|
||||
@@ -90,6 +91,15 @@ public class TransitionUtil {
|
||||
&& !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
|
||||
}
|
||||
|
||||
/** Returns `true` if `change` is only re-ordering. */
|
||||
public static boolean isOrderOnly(TransitionInfo.Change change) {
|
||||
return change.getMode() == TRANSIT_CHANGE
|
||||
&& (change.getFlags() & FLAG_MOVED_TO_TOP) != 0
|
||||
&& change.getStartAbsBounds().equals(change.getEndAbsBounds())
|
||||
&& (change.getLastParent() == null
|
||||
|| change.getLastParent().equals(change.getParent()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter that selects leaf-tasks only. THIS IS ORDER-DEPENDENT! For it to work properly, you
|
||||
* MUST call `test` in the same order that the changes appear in the TransitionInfo.
|
||||
|
||||
@@ -63,6 +63,7 @@ public class RemoteAnimationTargetCompat {
|
||||
final ArrayList<RemoteAnimationTarget> out = new ArrayList<>();
|
||||
for (int i = 0; i < info.getChanges().size(); i++) {
|
||||
TransitionInfo.Change change = info.getChanges().get(i);
|
||||
if (TransitionUtil.isOrderOnly(change)) continue;
|
||||
if (filter.test(change)) {
|
||||
out.add(TransitionUtil.newTarget(
|
||||
change, info.getChanges().size() - i, info, t, leashMap));
|
||||
|
||||
@@ -4687,6 +4687,7 @@ class Task extends TaskFragment {
|
||||
if (!isAttached()) {
|
||||
return;
|
||||
}
|
||||
mTransitionController.collect(this);
|
||||
|
||||
final TaskDisplayArea taskDisplayArea = getDisplayArea();
|
||||
|
||||
|
||||
@@ -50,6 +50,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_MOVED_TO_TOP;
|
||||
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
|
||||
import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
|
||||
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
|
||||
@@ -182,6 +183,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
/** The displays that this transition is running on. */
|
||||
private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The (non alwaysOnTop) tasks which were on-top of their display before the transition. If
|
||||
* tasks are nested, all the tasks that are parents of the on-top task are also included.
|
||||
*/
|
||||
private final ArrayList<Task> mOnTopTasksStart = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Set of participating windowtokens (activity/wallpaper) which are visible at the end of
|
||||
* the transition animation.
|
||||
@@ -515,6 +522,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
mParticipants.add(wc);
|
||||
if (wc.getDisplayContent() != null && !mTargetDisplays.contains(wc.getDisplayContent())) {
|
||||
mTargetDisplays.add(wc.getDisplayContent());
|
||||
addOnTopTasks(wc.getDisplayContent(), mOnTopTasksStart);
|
||||
}
|
||||
if (info.mShowWallpaper) {
|
||||
// Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
|
||||
@@ -526,6 +534,27 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds the top non-alwaysOnTop tasks within `task` to `out`. */
|
||||
private static void addOnTopTasks(Task task, ArrayList<Task> out) {
|
||||
for (int i = task.getChildCount() - 1; i >= 0; --i) {
|
||||
final Task child = task.getChildAt(i).asTask();
|
||||
if (child == null) return;
|
||||
if (child.getWindowConfiguration().isAlwaysOnTop()) continue;
|
||||
out.add(child);
|
||||
addOnTopTasks(child, out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the top non-alwaysOnTop leaf task on the display `dc`. */
|
||||
private static void addOnTopTasks(DisplayContent dc, ArrayList<Task> out) {
|
||||
final Task topNotAlwaysOnTop = dc.getRootTask(
|
||||
t -> !t.getWindowConfiguration().isAlwaysOnTop());
|
||||
if (topNotAlwaysOnTop == null) return;
|
||||
out.add(topNotAlwaysOnTop);
|
||||
addOnTopTasks(topNotAlwaysOnTop, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records wc as changing its state of existence during this transition. For example, a new
|
||||
* task is considered an existence change while moving a task to front is not. wc is added
|
||||
@@ -997,11 +1026,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
InsetsControlTarget prevImeTarget = dc.getImeTarget(
|
||||
DisplayContent.IME_TARGET_CONTROL);
|
||||
InsetsControlTarget newImeTarget = null;
|
||||
TaskDisplayArea transientTDA = null;
|
||||
// Transient-launch activities cannot be IME target (WindowState#canBeImeTarget),
|
||||
// so re-compute in case the IME target is changed after transition.
|
||||
for (int t = 0; t < mTransientLaunches.size(); ++t) {
|
||||
if (mTransientLaunches.keyAt(t).getDisplayContent() == dc) {
|
||||
newImeTarget = dc.computeImeTarget(true /* updateImeTarget */);
|
||||
transientTDA = mTransientLaunches.keyAt(i).getTaskDisplayArea();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1012,6 +1043,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
InputMethodManagerInternal.get().updateImeWindowStatus(
|
||||
false /* disableImeIcon */);
|
||||
}
|
||||
// An uncommitted transient launch can leave incomplete lifecycles if visibilities
|
||||
// didn't change (eg. re-ordering with translucent tasks will leave launcher
|
||||
// in RESUMED state), so force an update here.
|
||||
if (!hasVisibleTransientLaunch && transientTDA != null) {
|
||||
transientTDA.pauseBackTasks(null /* resuming */);
|
||||
}
|
||||
}
|
||||
dc.removeImeSurfaceImmediately();
|
||||
dc.handleCompleteDeferredRemoval();
|
||||
@@ -1137,6 +1174,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
}
|
||||
// Check whether the participants were animated from back navigation.
|
||||
mController.mAtm.mBackNavigationController.onTransactionReady(this);
|
||||
|
||||
collectOrderChanges();
|
||||
|
||||
// Resolve the animating targets from the participants.
|
||||
mTargets = calculateTargets(mParticipants, mChanges);
|
||||
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
|
||||
@@ -1288,6 +1328,27 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
info.releaseAnimSurfaces();
|
||||
}
|
||||
|
||||
/** Collect tasks which moved-to-top but didn't change otherwise. */
|
||||
@VisibleForTesting
|
||||
void collectOrderChanges() {
|
||||
if (mOnTopTasksStart.isEmpty()) return;
|
||||
final ArrayList<Task> onTopTasksEnd = new ArrayList<>();
|
||||
for (int i = 0; i < mTargetDisplays.size(); ++i) {
|
||||
addOnTopTasks(mTargetDisplays.get(i), onTopTasksEnd);
|
||||
}
|
||||
for (int i = 0; i < onTopTasksEnd.size(); ++i) {
|
||||
final Task task = onTopTasksEnd.get(i);
|
||||
if (mOnTopTasksStart.contains(task)) continue;
|
||||
mParticipants.add(task);
|
||||
int changeIdx = mChanges.indexOfKey(task);
|
||||
if (changeIdx < 0) {
|
||||
mChanges.put(task, new ChangeInfo(task));
|
||||
changeIdx = mChanges.indexOfKey(task);
|
||||
}
|
||||
mChanges.valueAt(changeIdx).mFlags |= ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP;
|
||||
}
|
||||
}
|
||||
|
||||
private void postCleanupOnFailure() {
|
||||
mController.mAtm.mH.post(() -> {
|
||||
synchronized (mController.mAtm.mGlobalLock) {
|
||||
@@ -2243,13 +2304,17 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
*/
|
||||
private static final int FLAG_CHANGE_YES_ANIMATION = 0x10;
|
||||
|
||||
/** Whether this change's container moved to the top. */
|
||||
private static final int FLAG_CHANGE_MOVED_TO_TOP = 0x20;
|
||||
|
||||
@IntDef(prefix = { "FLAG_" }, value = {
|
||||
FLAG_NONE,
|
||||
FLAG_SEAMLESS_ROTATION,
|
||||
FLAG_TRANSIENT_LAUNCH,
|
||||
FLAG_ABOVE_TRANSIENT_LAUNCH,
|
||||
FLAG_CHANGE_NO_ANIMATION,
|
||||
FLAG_CHANGE_YES_ANIMATION
|
||||
FLAG_CHANGE_YES_ANIMATION,
|
||||
FLAG_CHANGE_MOVED_TO_TOP
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface Flag {}
|
||||
@@ -2280,7 +2345,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
int mDisplayId = -1;
|
||||
@ActivityInfo.Config int mKnownConfigChanges;
|
||||
|
||||
/** These are just extra info. They aren't used for change-detection. */
|
||||
/** Extra information about this change. */
|
||||
@Flag int mFlags = FLAG_NONE;
|
||||
|
||||
/** Snapshot surface and luma, if relevant. */
|
||||
@@ -2332,7 +2397,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
|| (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode)
|
||||
|| !mContainer.getBounds().equals(mAbsoluteBounds)
|
||||
|| mRotation != mContainer.getWindowConfiguration().getRotation()
|
||||
|| mDisplayId != getDisplayId(mContainer);
|
||||
|| mDisplayId != getDisplayId(mContainer)
|
||||
|| (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0;
|
||||
}
|
||||
|
||||
@TransitionInfo.TransitionMode
|
||||
@@ -2433,6 +2499,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
&& (mFlags & FLAG_CHANGE_YES_ANIMATION) == 0) {
|
||||
flags |= FLAG_NO_ANIMATION;
|
||||
}
|
||||
if ((mFlags & FLAG_CHANGE_MOVED_TO_TOP) != 0) {
|
||||
flags |= FLAG_MOVED_TO_TOP;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
||||
@@ -1885,6 +1885,39 @@ public class TransitionTests extends WindowTestsBase {
|
||||
assertEquals(newParent.getDisplayArea(), change.mCommonAncestor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveToTopWhileVisible() {
|
||||
final Transition transition = createTestTransition(TRANSIT_OPEN);
|
||||
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
|
||||
final ArraySet<WindowContainer> participants = transition.mParticipants;
|
||||
|
||||
// Start with taskB on top and taskA on bottom but both visible.
|
||||
final Task rootTaskA = createTask(mDisplayContent);
|
||||
final Task leafTaskA = createTaskInRootTask(rootTaskA, 0 /* userId */);
|
||||
final Task taskB = createTask(mDisplayContent);
|
||||
leafTaskA.setVisibleRequested(true);
|
||||
taskB.setVisibleRequested(true);
|
||||
// manually collect since this is a test transition and not known by transitionController.
|
||||
transition.collect(leafTaskA);
|
||||
rootTaskA.moveToFront("test", leafTaskA);
|
||||
|
||||
// All the tasks were already visible, so there shouldn't be any changes
|
||||
ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
|
||||
participants, changes);
|
||||
assertTrue(targets.isEmpty());
|
||||
|
||||
// After collecting order changes, it should recognize that a task moved to top.
|
||||
transition.collectOrderChanges();
|
||||
targets = Transition.calculateTargets(participants, changes);
|
||||
assertEquals(1, targets.size());
|
||||
|
||||
// Make sure the flag is set
|
||||
final TransitionInfo info = Transition.calculateTransitionInfo(
|
||||
transition.mType, 0 /* flags */, targets, mMockT);
|
||||
assertTrue((info.getChanges().get(0).getFlags() & TransitionInfo.FLAG_MOVED_TO_TOP) != 0);
|
||||
assertEquals(TRANSIT_CHANGE, info.getChanges().get(0).getMode());
|
||||
}
|
||||
|
||||
private static void makeTaskOrganized(Task... tasks) {
|
||||
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
|
||||
for (Task t : tasks) {
|
||||
|
||||
Reference in New Issue
Block a user