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

Commit f646ee11 authored by Jiaming Liu's avatar Jiaming Liu
Browse files

Allow TaskFragment to move to bottom when clear top

Add TaskFragment op OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH
If set to true, when a launching activity clears top, the TaskFragment
is moved bottom of the Task instead of being finished.

Bug: 317896352
Test: atest TaskTests
Change-Id: I4c7e11f82fd26adda20233f6dfd954beb896c72b
parent 55e27576
Loading
Loading
Loading
Loading
+43 −4
Original line number Diff line number Diff line
@@ -125,6 +125,16 @@ public final class TaskFragmentOperation implements Parcelable {
     */
    public static final int OP_TYPE_SET_DIM_ON_TASK = 16;

    /**
     * Sets this TaskFragment to move to bottom of the Task if any of the activities below it is
     * launched in a mode requiring clear top.
     *
     * This is only allowed for system organizers. See
     * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
     * ITaskFragmentOrganizer, boolean)}
     */
    public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17;

    @IntDef(prefix = { "OP_TYPE_" }, value = {
            OP_TYPE_UNKNOWN,
            OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -144,6 +154,7 @@ public final class TaskFragmentOperation implements Parcelable {
            OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE,
            OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
            OP_TYPE_SET_DIM_ON_TASK,
            OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface OperationType {}
@@ -173,12 +184,14 @@ public final class TaskFragmentOperation implements Parcelable {

    private final boolean mDimOnTask;

    private final boolean mMoveToBottomIfClearWhenLaunch;

    private TaskFragmentOperation(@OperationType int opType,
            @Nullable TaskFragmentCreationParams taskFragmentCreationParams,
            @Nullable IBinder activityToken, @Nullable Intent activityIntent,
            @Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
            @Nullable TaskFragmentAnimationParams animationParams,
            boolean isolatedNav, boolean dimOnTask) {
            boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch) {
        mOpType = opType;
        mTaskFragmentCreationParams = taskFragmentCreationParams;
        mActivityToken = activityToken;
@@ -188,6 +201,7 @@ public final class TaskFragmentOperation implements Parcelable {
        mAnimationParams = animationParams;
        mIsolatedNav = isolatedNav;
        mDimOnTask = dimOnTask;
        mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
    }

    private TaskFragmentOperation(Parcel in) {
@@ -200,6 +214,7 @@ public final class TaskFragmentOperation implements Parcelable {
        mAnimationParams = in.readTypedObject(TaskFragmentAnimationParams.CREATOR);
        mIsolatedNav = in.readBoolean();
        mDimOnTask = in.readBoolean();
        mMoveToBottomIfClearWhenLaunch = in.readBoolean();
    }

    @Override
@@ -213,6 +228,7 @@ public final class TaskFragmentOperation implements Parcelable {
        dest.writeTypedObject(mAnimationParams, flags);
        dest.writeBoolean(mIsolatedNav);
        dest.writeBoolean(mDimOnTask);
        dest.writeBoolean(mMoveToBottomIfClearWhenLaunch);
    }

    @NonNull
@@ -300,6 +316,14 @@ public final class TaskFragmentOperation implements Parcelable {
        return mDimOnTask;
    }

    /**
     * Returns whether the TaskFragment should move to bottom of task when any activity below it
     * is launched in clear top mode.
     */
    public boolean isMoveToBottomIfClearWhenLaunch() {
        return mMoveToBottomIfClearWhenLaunch;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
@@ -324,6 +348,7 @@ public final class TaskFragmentOperation implements Parcelable {
        }
        sb.append(", isolatedNav=").append(mIsolatedNav);
        sb.append(", dimOnTask=").append(mDimOnTask);
        sb.append(", moveToBottomIfClearWhenLaunch=").append(mMoveToBottomIfClearWhenLaunch);

        sb.append('}');
        return sb.toString();
@@ -332,7 +357,8 @@ public final class TaskFragmentOperation implements Parcelable {
    @Override
    public int hashCode() {
        return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
                mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask);
                mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask,
                mMoveToBottomIfClearWhenLaunch);
    }

    @Override
@@ -349,7 +375,8 @@ public final class TaskFragmentOperation implements Parcelable {
                && Objects.equals(mSecondaryFragmentToken, other.mSecondaryFragmentToken)
                && Objects.equals(mAnimationParams, other.mAnimationParams)
                && mIsolatedNav == other.mIsolatedNav
                && mDimOnTask == other.mDimOnTask;
                && mDimOnTask == other.mDimOnTask
                && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch;
    }

    @Override
@@ -385,6 +412,8 @@ public final class TaskFragmentOperation implements Parcelable {

        private boolean mDimOnTask;

        private boolean mMoveToBottomIfClearWhenLaunch;

        /**
         * @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
         */
@@ -465,6 +494,16 @@ public final class TaskFragmentOperation implements Parcelable {
            return this;
        }

        /**
         * Sets whether the TaskFragment should move to bottom of task when any activity below it
         * is launched in clear top mode.
         */
        @NonNull
        public Builder setMoveToBottomIfClearWhenLaunch(boolean moveToBottomIfClearWhenLaunch) {
            mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
            return this;
        }

        /**
         * Constructs the {@link TaskFragmentOperation}.
         */
@@ -472,7 +511,7 @@ public final class TaskFragmentOperation implements Parcelable {
        public TaskFragmentOperation build() {
            return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
                    mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
                    mIsolatedNav, mDimOnTask);
                    mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch);
        }
    }
}
+47 −0
Original line number Diff line number Diff line
@@ -208,6 +208,7 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -1702,6 +1703,8 @@ class Task extends TaskFragment {
        final ActivityRecord r = findActivityInHistory(newR.mActivityComponent, newR.mUserId);
        if (r == null) return null;

        moveTaskFragmentsToBottomIfNeeded(r, finishCount);

        final PooledPredicate f = PooledLambda.obtainPredicate(
                (ActivityRecord ar, ActivityRecord boundaryActivity) ->
                        finishActivityAbove(ar, boundaryActivity, finishCount),
@@ -1722,6 +1725,50 @@ class Task extends TaskFragment {
        return r;
    }

    /**
     * Moves {@link TaskFragment}s to the bottom if the flag
     * {@link TaskFragment#isMoveToBottomIfClearWhenLaunch} is {@code true}.
     */
    @VisibleForTesting
    void moveTaskFragmentsToBottomIfNeeded(@NonNull ActivityRecord r, @NonNull int[] finishCount) {
        final int activityIndex = mChildren.indexOf(r);
        if (activityIndex < 0) {
            return;
        }

        List<TaskFragment> taskFragmentsToMove = null;

        // Find the TaskFragments that need to be moved
        for (int i = mChildren.size() - 1; i > activityIndex; i--) {
            final TaskFragment taskFragment = mChildren.get(i).asTaskFragment();
            if (taskFragment != null && taskFragment.isMoveToBottomIfClearWhenLaunch()) {
                if (taskFragmentsToMove == null) {
                    taskFragmentsToMove = new ArrayList<>();
                }
                taskFragmentsToMove.add(taskFragment);
            }
        }
        if (taskFragmentsToMove == null) {
            return;
        }

        // Move the TaskFragments to the bottom of the Task. Their relative orders are preserved.
        final int size = taskFragmentsToMove.size();
        for (int i = 0; i < size; i++) {
            final TaskFragment taskFragment = taskFragmentsToMove.get(i);

            // The visibility of the TaskFragment may change. Collect it in the transition so that
            // transition animation can be properly played.
            mTransitionController.collect(taskFragment);

            positionChildAt(POSITION_BOTTOM, taskFragment, false /* includeParents */);
        }

        // Treat it as if the TaskFragments are finished so that a transition animation can be
        // played to send the TaskFragments back and bring the activity to front.
        finishCount[0] += size;
    }

    private static boolean finishActivityAbove(ActivityRecord r, ActivityRecord boundaryActivity,
            @NonNull int[] finishCount) {
        // Stop operation once we reach the boundary activity.
+14 −0
Original line number Diff line number Diff line
@@ -363,6 +363,12 @@ class TaskFragment extends WindowContainer<WindowContainer> {
     */
    private boolean mIsolatedNav;

    /**
     * Whether the TaskFragment should move to bottom of task when any activity below it is
     * launched in clear top mode.
     */
    private boolean mMoveToBottomIfClearWhenLaunch;

    /** When set, will force the task to report as invisible. */
    static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
    static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
@@ -3045,6 +3051,14 @@ class TaskFragment extends WindowContainer<WindowContainer> {
        mEmbeddedDimArea = embeddedDimArea;
    }

    void setMoveToBottomIfClearWhenLaunch(boolean moveToBottomIfClearWhenLaunch) {
        mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
    }

    boolean isMoveToBottomIfClearWhenLaunch() {
        return mMoveToBottomIfClearWhenLaunch;
    }

    @VisibleForTesting
    boolean isDimmingOnParentTask() {
        return mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK;
+17 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
@@ -1502,6 +1503,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
                        : EMBEDDED_DIM_AREA_TASK_FRAGMENT);
                break;
            }
            case OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH: {
                taskFragment.setMoveToBottomIfClearWhenLaunch(
                        operation.isMoveToBottomIfClearWhenLaunch());
                break;
            }
        }
        return effects;
    }
@@ -1554,6 +1560,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
            return false;
        }

        if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH)
                && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
            final Throwable exception = new SecurityException(
                    "Only a system organizer can perform "
                            + "OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH."
            );
            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
                    opType, exception);
            return false;
        }

        final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken();
        return secondaryFragmentToken == null
                || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType,
+29 −0
Original line number Diff line number Diff line
@@ -1822,6 +1822,35 @@ public class TaskTests extends WindowTestsBase {
        verify(fragment2).assignLayer(t, 2);
    }

    @Test
    public void testMoveTaskFragmentsToBottomIfNeeded() {
        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord unembeddedActivity = task.getTopMostActivity();

        final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
        final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
        final TaskFragment fragment3 = createTaskFragmentWithEmbeddedActivity(task, organizer);
        doReturn(true).when(fragment1).isMoveToBottomIfClearWhenLaunch();
        doReturn(false).when(fragment2).isMoveToBottomIfClearWhenLaunch();
        doReturn(true).when(fragment3).isMoveToBottomIfClearWhenLaunch();

        assertEquals(unembeddedActivity, task.mChildren.get(0));
        assertEquals(fragment1, task.mChildren.get(1));
        assertEquals(fragment2, task.mChildren.get(2));
        assertEquals(fragment3, task.mChildren.get(3));

        final int[] finishCount = {0};
        task.moveTaskFragmentsToBottomIfNeeded(unembeddedActivity, finishCount);

        // fragment1 and fragment3 should be moved to the bottom of the task
        assertEquals(fragment1, task.mChildren.get(0));
        assertEquals(fragment3, task.mChildren.get(1));
        assertEquals(unembeddedActivity, task.mChildren.get(2));
        assertEquals(fragment2, task.mChildren.get(3));
        assertEquals(2, finishCount[0]);
    }

    private Task getTestTask() {
        return new TaskBuilder(mSupervisor).setCreateActivity(true).build();
    }