Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a361b4d9 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Handle relaunching pausing desktop tasks" into main

parents 43314c7d f4e2342f
Loading
Loading
Loading
Loading
+73 −22
Original line number Diff line number Diff line
@@ -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;
@@ -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;

@@ -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;
@@ -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");
@@ -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");
@@ -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;
@@ -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<>();
                        }
@@ -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");
@@ -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();
@@ -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.
@@ -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;
@@ -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,
@@ -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;
@@ -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);
                }
@@ -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
@@ -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);
                }
@@ -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;
            }
+114 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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;

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