Loading libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +73 −22 Original line number Diff line number Diff line Loading @@ -64,6 +64,7 @@ import android.util.Pair; import android.util.Slog; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.DesktopExperienceFlags; import android.window.PictureInPictureSurfaceTransaction; import android.window.TaskSnapshot; import android.window.TransitionInfo; Loading @@ -90,6 +91,7 @@ import com.android.wm.shell.transition.HomeTransitionObserver; import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; Loading Loading @@ -387,8 +389,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, private SurfaceControl.Transaction mFinishTransaction = null; /** * List of tasks that we are switching away from via this transition. Upon finish, these * pausing tasks will become invisible. * List of tasks that we are switching away from via this transition, ordered from top most * to bottom most in z-order. Upon finish, these pausing tasks will become invisible. * These need to be ordered since the order must be restored if there is no task-switch. */ private ArrayList<TaskState> mPausingTasks = null; Loading Loading @@ -1026,6 +1028,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); if (taskInfo != null && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Canceling due to always on top task", mInstanceId); // Tasks that are always on top (e.g. bubbles), will handle their own transition // as they are on top of everything else. So cancel the merge here. cancel("task #" + taskInfo.taskId + " is always_on_top"); Loading @@ -1033,6 +1037,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, } if (TransitionUtil.isClosingType(change.getMode()) && taskInfo != null && taskInfo.lastParentTaskIdBeforePip > 0) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Canceling due to restoring PIP activity", mInstanceId); // Pinned task is closing as a side effect of the removal of its original Task, // such transition should be handled by PiP. So cancel the merge here. cancel("task #" + taskInfo.taskId + " is removed with its original parent"); Loading @@ -1046,9 +1052,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, final boolean isLeafTask = leafTaskFilter.test(change); if (TransitionUtil.isOpeningType(change.getMode()) || TransitionUtil.isOrderOnly(change)) { final String chgTypeMsg = TransitionUtil.isOpeningType(change.getMode()) ? "Opening" : "Changing"; if (isRecentsTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] %s recents", mInstanceId, chgTypeMsg); recentsOpening = change; } else if (isRootTask || isLeafTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] %s task", mInstanceId, chgTypeMsg); if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { // This is usually a 3p launcher mOpeningSeparateHome = true; Loading @@ -1062,8 +1074,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, } } else if (TransitionUtil.isClosingType(change.getMode())) { if (isRecentsTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Closing recents", mInstanceId); foundRecentsClosing = true; } else if (isRootTask || isLeafTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Closing task", mInstanceId); if (closingTasks == null) { closingTasks = new ArrayList<>(); } Loading @@ -1074,6 +1090,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, // transition handler can play the animation such as rotation effect. if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY) && info.getType() == TRANSIT_CHANGE) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Canceling due to display change", mInstanceId); // This call to cancel will use the screenshots taken preemptively in // handleMidTransitionRequest() prior to the display changing cancel(true /* toHome */, true /* withScreenshots */, "display change"); Loading @@ -1089,6 +1107,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, && taskInfo != null && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Moving task to fullscreen", mInstanceId); if (openingTasks == null) { openingTasks = new ArrayList<>(); openingTaskIsLeafs = new IntArray(); Loading @@ -1098,6 +1118,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, } } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME && !isRecentsTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Opening home", mInstanceId); // Unless it is a 3p launcher. This means that the 3p launcher was already // visible (eg. the "pausing" task is translucent over the 3p launcher). // Treat it as if we are "re-opening" the 3p launcher. Loading Loading @@ -1165,20 +1187,20 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, didMergeThings = true; } } RemoteAnimationTarget[] appearedTargets = null; ArrayList<RemoteAnimationTarget> appearedTargets = null; if (openingTasks != null && openingTasks.size() > 0) { // Switching to some new tasks, add to mOpening and remove from mPausing. Also, // enter NEW_TASK state since this will start the switch-to animation. final int layer = mInfo.getChanges().size() * 3; int openingLeafCount = 0; for (int i = 0; i < openingTaskIsLeafs.size(); ++i) { openingLeafCount += openingTaskIsLeafs.get(i); if (openingTaskIsLeafs.get(i) > 0) { // There is at least one opening leaf task, so initialize the appeared // targets list appearedTargets = new ArrayList<>(); break; } if (openingLeafCount > 0) { appearedTargets = new RemoteAnimationTarget[openingLeafCount]; } boolean onlyOpeningPausedTasks = true; int nextTargetIdx = 0; for (int i = 0; i < openingTasks.size(); ++i) { final TransitionInfo.Change change = openingTasks.get(i); final boolean isLeaf = openingTaskIsLeafs.get(i) == 1; Loading @@ -1191,8 +1213,18 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, if (pausingIdx >= 0) { // Something is showing/opening a previously-pausing app. if (isLeaf) { appearedTargets[nextTargetIdx++] = TransitionUtil.newTarget( change, layer, mPausingTasks.get(pausingIdx).mLeash); appearedTargets.add(TransitionUtil.newTarget( change, layer, mPausingTasks.get(pausingIdx).mLeash)); // In multi-desk mode, we can have a root task which the leaf tasks are // relative to, but if both the root and leaf are a part of the // transition then Transitions#setupStartState() will incorrectly apply // an offset to the task surface which is already leashed. Unlike the // start of the transition, we don't end up going through // TransitionUtil#createLeash(), which normally resets the position of // the task within the leash, so we have to do it manually here if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) { startT.setPosition(change.getLeash(), /* x= */ 0, /* y= */ 0); } } final TaskState pausingTask = mPausingTasks.remove(pausingIdx); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, Loading @@ -1207,7 +1239,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, // We are receiving new opening leaf tasks, so convert to onTasksAppeared. final RemoteAnimationTarget target = TransitionUtil.newTarget( change, layer, info, startT, mLeashMap); appearedTargets[nextTargetIdx++] = target; appearedTargets.add(target); // reparent into the original `mInfo` since that's where we are animating. final TransitionInfo.Root root = TransitionUtil.getRootFor(change, mInfo); final boolean wasClosing = closingIdx >= 0; Loading Loading @@ -1282,7 +1314,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, try { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] RecentsController.merge: calling onTasksAppeared", mInstanceId); mListener.onTasksAppeared(appearedTargets, passTransitionInfo ? info : null); final RemoteAnimationTarget[] targets = appearedTargets.toArray( new RemoteAnimationTarget[0]); mListener.onTasksAppeared(targets, passTransitionInfo ? info : null); } catch (RemoteException e) { Slog.e(TAG, "Error sending appeared tasks to recents animation", e); } Loading Loading @@ -1398,6 +1432,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, return; } // Note: returning to a desk tile, whether it be by clicking the tile or one of the // existing exploded tasks is considered to be returning to app. boolean returningToApp = !toHome && !mWillFinishToHome && mPausingTasks != null Loading Loading @@ -1442,15 +1478,30 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, if (returningToApp) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app"); // The gesture is returning to the pausing-task(s) rather than continuing with // recents, so end the transition by moving the app back to the top (and also // re-showing it's task). for (int i = mPausingTasks.size() - 1; i >= 0; --i) { // recents, so end the transition by moving the app(s) back to the top (and also // re-showing their tasks). final List<TaskState> tasksToShowFrontToBack = new ArrayList<>(); if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) { // Opening tasks can also exist in a |returningToApp| case, such as when // returning to the desk tile by clicking on one of the exploded view tasks. // These are really "pausing" tasks that became "opening" because they were // re-brought to front. They should be on top of pausing tasks, so insert them // first. tasksToShowFrontToBack.addAll(mOpeningTasks); } // The remaining pausing tasks should also be moved back to top, but below the // opening ones. tasksToShowFrontToBack.addAll(mPausingTasks); for (int i = tasksToShowFrontToBack.size() - 1; i >= 0; --i) { // reverse order so that index 0 ends up on top wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */); t.show(mPausingTasks.get(i).mTaskSurface); final TaskState taskState = tasksToShowFrontToBack.get(i); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " reorder task#%d to top", taskState.mTaskInfo.taskId); wct.reorder(taskState.mToken, true /* onTop */); t.show(taskState.mTaskSurface); } setCornerRadiusForFreeformTasks( mRecentTasksController.getContext(), t, mPausingTasks); setCornerRadiusForFreeformTasks(mRecentTasksController.getContext(), t, tasksToShowFrontToBack); if (!mKeyguardLocked && mRecentsTask != null) { wct.restoreTransientOrder(mRecentsTask); } Loading Loading @@ -1641,7 +1692,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, private void setCornerRadiusForFreeformTasks( Context context, SurfaceControl.Transaction t, ArrayList<TaskState> tasks) { List<TaskState> tasks) { if (!ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()) { return; } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java +114 −0 Original line number Diff line number Diff line Loading @@ -22,9 +22,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX; import static com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND; import static com.android.wm.shell.Flags.FLAG_ENABLE_PIP2; import static com.android.wm.shell.Flags.FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION; import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING; Loading @@ -36,7 +38,9 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; Loading @@ -62,6 +66,7 @@ import android.os.UserManager; import android.platform.test.annotations.EnableFlags; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; Loading Loading @@ -91,9 +96,12 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Arrays; import java.util.Optional; /** Loading Loading @@ -497,6 +505,100 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { startTransitionAndMergeThenVerifyCanceled(mergeTransitionInfo); } @Test @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testMergeAndFinish_openingTaskInDesk_setsPositionOfChild() { ActivityManager.RunningTaskInfo deskRootTask = new TestRunningTaskInfoBuilder() .setWindowingMode(WINDOWING_MODE_FREEFORM) .build(); ActivityManager.RunningTaskInfo deskChildTask = new TestRunningTaskInfoBuilder() .setWindowingMode(WINDOWING_MODE_FREEFORM) .setParentTaskId(deskRootTask.taskId) .build(); TransitionInfo mergeTransitionInfo = new TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN, deskChildTask) .addChange(TRANSIT_OPEN, deskRootTask) .build(); SurfaceControl deskChildLeash = mergeTransitionInfo.getChanges().get(0).getLeash(); final IBinder transition = startRecentsTransition(/* synthetic= */ false); SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); mRecentsTransitionHandler.setFinishTransactionSupplier(() -> finishT); mRecentsTransitionHandler.startAnimation( transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(), mock(Transitions.TransitionFinishCallback.class)); mRecentsTransitionHandler.findController(transition).merge( mergeTransitionInfo, startT, finishT, mock(Transitions.TransitionFinishCallback.class)); mRecentsTransitionHandler.findController(transition).finish(/* toHome= */ false, false /* sendUserLeaveHint */, mock(IResultReceiver.class)); mMainExecutor.flushAll(); verify(startT).setPosition(deskChildLeash, /* x= */ 0, /* y= */0); } @Test @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testMergeAndFinish_openingTaskInDeskWithSiblings_reordersAllToTop() { ActivityManager.RunningTaskInfo deskRootTask = new TestRunningTaskInfoBuilder() .setWindowingMode(WINDOWING_MODE_FREEFORM) .build(); ActivityManager.RunningTaskInfo deskChildTask1 = new TestRunningTaskInfoBuilder() .setWindowingMode(WINDOWING_MODE_FREEFORM) .setParentTaskId(deskRootTask.taskId) .build(); ActivityManager.RunningTaskInfo deskChildTask2 = new TestRunningTaskInfoBuilder() .setWindowingMode(WINDOWING_MODE_FREEFORM) .setParentTaskId(deskRootTask.taskId) .build(); TransitionInfo startTransitionInfo = new TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_TO_BACK, deskChildTask1) .addChange(TRANSIT_TO_BACK, deskChildTask2) .addChange(TRANSIT_TO_BACK, deskRootTask) .build(); TransitionInfo mergeTransitionInfo = new TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN, deskChildTask2) .addChange(TRANSIT_OPEN, deskRootTask) .build(); final IBinder transition = startRecentsTransition(/* synthetic= */ false); SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); mRecentsTransitionHandler.setFinishTransactionSupplier(() -> finishT); mRecentsTransitionHandler.startAnimation( transition, startTransitionInfo, new StubTransaction(), new StubTransaction(), mock(Transitions.TransitionFinishCallback.class)); mRecentsTransitionHandler.findController(transition).merge( mergeTransitionInfo, new StubTransaction(), finishT, mock(Transitions.TransitionFinishCallback.class)); mRecentsTransitionHandler.findController(transition).finish(/* toHome= */ false, false /* sendUserLeaveHint */, mock(IResultReceiver.class)); mMainExecutor.flushAll(); final ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction.class); verify(mTransitions) .startTransition(eq(TRANSIT_END_RECENTS_TRANSITION), wctCaptor.capture(), any()); final WindowContainerTransaction wct = wctCaptor.getValue(); assertNotNull(wct); // Task 2 was opened, so it should be on top. assertReorderInOrder(wct, new ArrayList<>(Arrays.asList(deskChildTask1, deskChildTask2))); // Both should be shown. SurfaceControl deskChild1Leash = startTransitionInfo.getChanges().get(0).getLeash(); SurfaceControl deskChild2Leash = startTransitionInfo.getChanges().get(1).getLeash(); verify(finishT).show(deskChild1Leash); verify(finishT).show(deskChild2Leash); } private void startTransitionAndMergeThenVerifyCanceled(TransitionInfo mergeTransition) throws Exception { final IRecentsAnimationRunner animationRunner = mock(IRecentsAnimationRunner.class); Loading Loading @@ -555,6 +657,18 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { .build(); } private void assertReorderInOrder(@NonNull WindowContainerTransaction wct, ArrayList<ActivityManager.RunningTaskInfo> tasks) { for (WindowContainerTransaction.HierarchyOp op : wct.getHierarchyOps()) { if (tasks.isEmpty()) break; if (op.getType() == WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER && op.getToTop() && op.getContainer().equals(tasks.get(0).token.asBinder())) { tasks.removeFirst(); } } assertTrue("Not all tasks were reordered to front in order", tasks.isEmpty()); } private static class TestTransitionStateListener implements RecentsTransitionStateListener { @RecentsTransitionState private int mState = TRANSITION_STATE_NOT_RUNNING; Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +73 −22 Original line number Diff line number Diff line Loading @@ -64,6 +64,7 @@ import android.util.Pair; import android.util.Slog; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.DesktopExperienceFlags; import android.window.PictureInPictureSurfaceTransaction; import android.window.TaskSnapshot; import android.window.TransitionInfo; Loading @@ -90,6 +91,7 @@ import com.android.wm.shell.transition.HomeTransitionObserver; import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; Loading Loading @@ -387,8 +389,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, private SurfaceControl.Transaction mFinishTransaction = null; /** * List of tasks that we are switching away from via this transition. Upon finish, these * pausing tasks will become invisible. * List of tasks that we are switching away from via this transition, ordered from top most * to bottom most in z-order. Upon finish, these pausing tasks will become invisible. * These need to be ordered since the order must be restored if there is no task-switch. */ private ArrayList<TaskState> mPausingTasks = null; Loading Loading @@ -1026,6 +1028,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); if (taskInfo != null && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Canceling due to always on top task", mInstanceId); // Tasks that are always on top (e.g. bubbles), will handle their own transition // as they are on top of everything else. So cancel the merge here. cancel("task #" + taskInfo.taskId + " is always_on_top"); Loading @@ -1033,6 +1037,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, } if (TransitionUtil.isClosingType(change.getMode()) && taskInfo != null && taskInfo.lastParentTaskIdBeforePip > 0) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Canceling due to restoring PIP activity", mInstanceId); // Pinned task is closing as a side effect of the removal of its original Task, // such transition should be handled by PiP. So cancel the merge here. cancel("task #" + taskInfo.taskId + " is removed with its original parent"); Loading @@ -1046,9 +1052,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, final boolean isLeafTask = leafTaskFilter.test(change); if (TransitionUtil.isOpeningType(change.getMode()) || TransitionUtil.isOrderOnly(change)) { final String chgTypeMsg = TransitionUtil.isOpeningType(change.getMode()) ? "Opening" : "Changing"; if (isRecentsTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] %s recents", mInstanceId, chgTypeMsg); recentsOpening = change; } else if (isRootTask || isLeafTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] %s task", mInstanceId, chgTypeMsg); if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { // This is usually a 3p launcher mOpeningSeparateHome = true; Loading @@ -1062,8 +1074,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, } } else if (TransitionUtil.isClosingType(change.getMode())) { if (isRecentsTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Closing recents", mInstanceId); foundRecentsClosing = true; } else if (isRootTask || isLeafTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Closing task", mInstanceId); if (closingTasks == null) { closingTasks = new ArrayList<>(); } Loading @@ -1074,6 +1090,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, // transition handler can play the animation such as rotation effect. if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY) && info.getType() == TRANSIT_CHANGE) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Canceling due to display change", mInstanceId); // This call to cancel will use the screenshots taken preemptively in // handleMidTransitionRequest() prior to the display changing cancel(true /* toHome */, true /* withScreenshots */, "display change"); Loading @@ -1089,6 +1107,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, && taskInfo != null && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Moving task to fullscreen", mInstanceId); if (openingTasks == null) { openingTasks = new ArrayList<>(); openingTaskIsLeafs = new IntArray(); Loading @@ -1098,6 +1118,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, } } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME && !isRecentsTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] Opening home", mInstanceId); // Unless it is a 3p launcher. This means that the 3p launcher was already // visible (eg. the "pausing" task is translucent over the 3p launcher). // Treat it as if we are "re-opening" the 3p launcher. Loading Loading @@ -1165,20 +1187,20 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, didMergeThings = true; } } RemoteAnimationTarget[] appearedTargets = null; ArrayList<RemoteAnimationTarget> appearedTargets = null; if (openingTasks != null && openingTasks.size() > 0) { // Switching to some new tasks, add to mOpening and remove from mPausing. Also, // enter NEW_TASK state since this will start the switch-to animation. final int layer = mInfo.getChanges().size() * 3; int openingLeafCount = 0; for (int i = 0; i < openingTaskIsLeafs.size(); ++i) { openingLeafCount += openingTaskIsLeafs.get(i); if (openingTaskIsLeafs.get(i) > 0) { // There is at least one opening leaf task, so initialize the appeared // targets list appearedTargets = new ArrayList<>(); break; } if (openingLeafCount > 0) { appearedTargets = new RemoteAnimationTarget[openingLeafCount]; } boolean onlyOpeningPausedTasks = true; int nextTargetIdx = 0; for (int i = 0; i < openingTasks.size(); ++i) { final TransitionInfo.Change change = openingTasks.get(i); final boolean isLeaf = openingTaskIsLeafs.get(i) == 1; Loading @@ -1191,8 +1213,18 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, if (pausingIdx >= 0) { // Something is showing/opening a previously-pausing app. if (isLeaf) { appearedTargets[nextTargetIdx++] = TransitionUtil.newTarget( change, layer, mPausingTasks.get(pausingIdx).mLeash); appearedTargets.add(TransitionUtil.newTarget( change, layer, mPausingTasks.get(pausingIdx).mLeash)); // In multi-desk mode, we can have a root task which the leaf tasks are // relative to, but if both the root and leaf are a part of the // transition then Transitions#setupStartState() will incorrectly apply // an offset to the task surface which is already leashed. Unlike the // start of the transition, we don't end up going through // TransitionUtil#createLeash(), which normally resets the position of // the task within the leash, so we have to do it manually here if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) { startT.setPosition(change.getLeash(), /* x= */ 0, /* y= */ 0); } } final TaskState pausingTask = mPausingTasks.remove(pausingIdx); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, Loading @@ -1207,7 +1239,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, // We are receiving new opening leaf tasks, so convert to onTasksAppeared. final RemoteAnimationTarget target = TransitionUtil.newTarget( change, layer, info, startT, mLeashMap); appearedTargets[nextTargetIdx++] = target; appearedTargets.add(target); // reparent into the original `mInfo` since that's where we are animating. final TransitionInfo.Root root = TransitionUtil.getRootFor(change, mInfo); final boolean wasClosing = closingIdx >= 0; Loading Loading @@ -1282,7 +1314,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, try { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "[%d] RecentsController.merge: calling onTasksAppeared", mInstanceId); mListener.onTasksAppeared(appearedTargets, passTransitionInfo ? info : null); final RemoteAnimationTarget[] targets = appearedTargets.toArray( new RemoteAnimationTarget[0]); mListener.onTasksAppeared(targets, passTransitionInfo ? info : null); } catch (RemoteException e) { Slog.e(TAG, "Error sending appeared tasks to recents animation", e); } Loading Loading @@ -1398,6 +1432,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, return; } // Note: returning to a desk tile, whether it be by clicking the tile or one of the // existing exploded tasks is considered to be returning to app. boolean returningToApp = !toHome && !mWillFinishToHome && mPausingTasks != null Loading Loading @@ -1442,15 +1478,30 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, if (returningToApp) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app"); // The gesture is returning to the pausing-task(s) rather than continuing with // recents, so end the transition by moving the app back to the top (and also // re-showing it's task). for (int i = mPausingTasks.size() - 1; i >= 0; --i) { // recents, so end the transition by moving the app(s) back to the top (and also // re-showing their tasks). final List<TaskState> tasksToShowFrontToBack = new ArrayList<>(); if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) { // Opening tasks can also exist in a |returningToApp| case, such as when // returning to the desk tile by clicking on one of the exploded view tasks. // These are really "pausing" tasks that became "opening" because they were // re-brought to front. They should be on top of pausing tasks, so insert them // first. tasksToShowFrontToBack.addAll(mOpeningTasks); } // The remaining pausing tasks should also be moved back to top, but below the // opening ones. tasksToShowFrontToBack.addAll(mPausingTasks); for (int i = tasksToShowFrontToBack.size() - 1; i >= 0; --i) { // reverse order so that index 0 ends up on top wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */); t.show(mPausingTasks.get(i).mTaskSurface); final TaskState taskState = tasksToShowFrontToBack.get(i); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " reorder task#%d to top", taskState.mTaskInfo.taskId); wct.reorder(taskState.mToken, true /* onTop */); t.show(taskState.mTaskSurface); } setCornerRadiusForFreeformTasks( mRecentTasksController.getContext(), t, mPausingTasks); setCornerRadiusForFreeformTasks(mRecentTasksController.getContext(), t, tasksToShowFrontToBack); if (!mKeyguardLocked && mRecentsTask != null) { wct.restoreTransientOrder(mRecentsTask); } Loading Loading @@ -1641,7 +1692,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, private void setCornerRadiusForFreeformTasks( Context context, SurfaceControl.Transaction t, ArrayList<TaskState> tasks) { List<TaskState> tasks) { if (!ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()) { return; } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java +114 −0 Original line number Diff line number Diff line Loading @@ -22,9 +22,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX; import static com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND; import static com.android.wm.shell.Flags.FLAG_ENABLE_PIP2; import static com.android.wm.shell.Flags.FLAG_ENABLE_RECENTS_BOOKEND_TRANSITION; import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING; Loading @@ -36,7 +38,9 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; Loading @@ -62,6 +66,7 @@ import android.os.UserManager; import android.platform.test.annotations.EnableFlags; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; Loading Loading @@ -91,9 +96,12 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Arrays; import java.util.Optional; /** Loading Loading @@ -497,6 +505,100 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { startTransitionAndMergeThenVerifyCanceled(mergeTransitionInfo); } @Test @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testMergeAndFinish_openingTaskInDesk_setsPositionOfChild() { ActivityManager.RunningTaskInfo deskRootTask = new TestRunningTaskInfoBuilder() .setWindowingMode(WINDOWING_MODE_FREEFORM) .build(); ActivityManager.RunningTaskInfo deskChildTask = new TestRunningTaskInfoBuilder() .setWindowingMode(WINDOWING_MODE_FREEFORM) .setParentTaskId(deskRootTask.taskId) .build(); TransitionInfo mergeTransitionInfo = new TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN, deskChildTask) .addChange(TRANSIT_OPEN, deskRootTask) .build(); SurfaceControl deskChildLeash = mergeTransitionInfo.getChanges().get(0).getLeash(); final IBinder transition = startRecentsTransition(/* synthetic= */ false); SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); mRecentsTransitionHandler.setFinishTransactionSupplier(() -> finishT); mRecentsTransitionHandler.startAnimation( transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(), mock(Transitions.TransitionFinishCallback.class)); mRecentsTransitionHandler.findController(transition).merge( mergeTransitionInfo, startT, finishT, mock(Transitions.TransitionFinishCallback.class)); mRecentsTransitionHandler.findController(transition).finish(/* toHome= */ false, false /* sendUserLeaveHint */, mock(IResultReceiver.class)); mMainExecutor.flushAll(); verify(startT).setPosition(deskChildLeash, /* x= */ 0, /* y= */0); } @Test @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testMergeAndFinish_openingTaskInDeskWithSiblings_reordersAllToTop() { ActivityManager.RunningTaskInfo deskRootTask = new TestRunningTaskInfoBuilder() .setWindowingMode(WINDOWING_MODE_FREEFORM) .build(); ActivityManager.RunningTaskInfo deskChildTask1 = new TestRunningTaskInfoBuilder() .setWindowingMode(WINDOWING_MODE_FREEFORM) .setParentTaskId(deskRootTask.taskId) .build(); ActivityManager.RunningTaskInfo deskChildTask2 = new TestRunningTaskInfoBuilder() .setWindowingMode(WINDOWING_MODE_FREEFORM) .setParentTaskId(deskRootTask.taskId) .build(); TransitionInfo startTransitionInfo = new TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_TO_BACK, deskChildTask1) .addChange(TRANSIT_TO_BACK, deskChildTask2) .addChange(TRANSIT_TO_BACK, deskRootTask) .build(); TransitionInfo mergeTransitionInfo = new TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN, deskChildTask2) .addChange(TRANSIT_OPEN, deskRootTask) .build(); final IBinder transition = startRecentsTransition(/* synthetic= */ false); SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); mRecentsTransitionHandler.setFinishTransactionSupplier(() -> finishT); mRecentsTransitionHandler.startAnimation( transition, startTransitionInfo, new StubTransaction(), new StubTransaction(), mock(Transitions.TransitionFinishCallback.class)); mRecentsTransitionHandler.findController(transition).merge( mergeTransitionInfo, new StubTransaction(), finishT, mock(Transitions.TransitionFinishCallback.class)); mRecentsTransitionHandler.findController(transition).finish(/* toHome= */ false, false /* sendUserLeaveHint */, mock(IResultReceiver.class)); mMainExecutor.flushAll(); final ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction.class); verify(mTransitions) .startTransition(eq(TRANSIT_END_RECENTS_TRANSITION), wctCaptor.capture(), any()); final WindowContainerTransaction wct = wctCaptor.getValue(); assertNotNull(wct); // Task 2 was opened, so it should be on top. assertReorderInOrder(wct, new ArrayList<>(Arrays.asList(deskChildTask1, deskChildTask2))); // Both should be shown. SurfaceControl deskChild1Leash = startTransitionInfo.getChanges().get(0).getLeash(); SurfaceControl deskChild2Leash = startTransitionInfo.getChanges().get(1).getLeash(); verify(finishT).show(deskChild1Leash); verify(finishT).show(deskChild2Leash); } private void startTransitionAndMergeThenVerifyCanceled(TransitionInfo mergeTransition) throws Exception { final IRecentsAnimationRunner animationRunner = mock(IRecentsAnimationRunner.class); Loading Loading @@ -555,6 +657,18 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { .build(); } private void assertReorderInOrder(@NonNull WindowContainerTransaction wct, ArrayList<ActivityManager.RunningTaskInfo> tasks) { for (WindowContainerTransaction.HierarchyOp op : wct.getHierarchyOps()) { if (tasks.isEmpty()) break; if (op.getType() == WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER && op.getToTop() && op.getContainer().equals(tasks.get(0).token.asBinder())) { tasks.removeFirst(); } } assertTrue("Not all tasks were reordered to front in order", tasks.isEmpty()); } private static class TestTransitionStateListener implements RecentsTransitionStateListener { @RecentsTransitionState private int mState = TRANSITION_STATE_NOT_RUNNING; Loading