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

Commit 20360acb authored by Simon (Qiong) Sun's avatar Simon (Qiong) Sun
Browse files

Support multiple tasks per split screen in SplitBounds

- Introduce `leftTopTaskIds` and `rightBottomTaskIds` lists to `SplitBounds`.
- Populate these lists by retrieving the relevant task IDs from Stage Tasks.

Bug: 346295292
Bug: 360782849
Flag: com.android.wm.shell.enable_flexible_two_app_split
Test: atest WMShellUnitTests

Change-Id: Ia644f831ae13618b10a275b76aeca4b0bc19cb46
parent 7531d411
Loading
Loading
Loading
Loading
+67 −16
Original line number Diff line number Diff line
@@ -15,12 +15,18 @@
 */
package com.android.wm.shell.shared.split;

import static android.app.ActivityTaskManager.INVALID_TASK_ID;

import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;

import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
@@ -55,13 +61,34 @@ public class SplitBounds implements Parcelable {
     * From seascape, it is the rightBottom task that expands slightly.
     */
    public final boolean initiatedFromSeascape;
    /** @deprecated Use {@link #leftTopTaskIds} instead. */
    @Deprecated
    public final int leftTopTaskId;
    /** @deprecated Use {@link #rightBottomTaskIds} instead. */
    @Deprecated
    public final int rightBottomTaskId;
    @NonNull
    public final List<Integer> leftTopTaskIds;
    @NonNull
    public final List<Integer> rightBottomTaskIds;

    public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
            int rightBottomTaskId, @PersistentSnapPosition int snapPosition) {
    public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds,
            int leftTopTaskId, int rightBottomTaskId,
            @NonNull List<Integer> leftTopTaskIds, @NonNull List<Integer> rightBottomTaskIds,
            @PersistentSnapPosition int snapPosition) {
        if (leftTopTaskId == INVALID_TASK_ID || rightBottomTaskId == INVALID_TASK_ID
                || leftTopTaskId == rightBottomTaskId
                || leftTopTaskIds.isEmpty() || rightBottomTaskIds.isEmpty()) {
            throw new IllegalArgumentException("The Split task ids are invalid:"
                    + " leftTopTaskId: " + leftTopTaskId
                    + " rightBottomTaskId: " + rightBottomTaskId
                    + " leftTopTaskId size: "  + leftTopTaskIds.size()
                    + " rightBottomTaskId size: " + rightBottomTaskIds.size());
        }
        this.leftTopBounds = leftTopBounds;
        this.rightBottomBounds = rightBottomBounds;
        this.leftTopTaskIds = List.copyOf(leftTopTaskIds);
        this.rightBottomTaskIds = List.copyOf(rightBottomTaskIds);
        this.leftTopTaskId = leftTopTaskId;
        this.rightBottomTaskId = rightBottomTaskId;
        this.snapPosition = snapPosition;
@@ -82,11 +109,7 @@ public class SplitBounds implements Parcelable {
            // all our current uses, but should be refactored.
            // TODO: Create a more reliable check, or refactor how splitting works on devices
            //  with insets.
            if (rightBottomBounds.width() > leftTopBounds.width()) {
                initiatedFromSeascape = true;
            } else {
                initiatedFromSeascape = false;
            }
            initiatedFromSeascape = rightBottomBounds.width() > leftTopBounds.width();
        }

        float totalWidth = rightBottomBounds.right - leftTopBounds.left;
@@ -97,6 +120,13 @@ public class SplitBounds implements Parcelable {
        dividerHeightPercent = visualDividerBounds.height() / totalHeight;
    }

    public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
            int rightBottomTaskId, @PersistentSnapPosition int snapPosition) {
        this(leftTopBounds, rightBottomBounds, leftTopTaskId, rightBottomTaskId,
                Collections.singletonList(leftTopTaskId),
                Collections.singletonList(rightBottomTaskId), snapPosition);
    }

    /**
     * Returns the percentage size of the left/top task (compared to the full width/height of
     * the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and the
@@ -138,11 +168,21 @@ public class SplitBounds implements Parcelable {
        leftTaskPercent = parcel.readFloat();
        appsStackedVertically = parcel.readBoolean();
        initiatedFromSeascape = parcel.readBoolean();
        leftTopTaskId = parcel.readInt();
        rightBottomTaskId = parcel.readInt();
        dividerWidthPercent = parcel.readFloat();
        dividerHeightPercent = parcel.readFloat();
        snapPosition = parcel.readInt();
        leftTopTaskId = parcel.readInt();
        rightBottomTaskId = parcel.readInt();
        int size = parcel.readInt();
        leftTopTaskIds = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            leftTopTaskIds.add(parcel.readInt());
        }
        size = parcel.readInt();
        rightBottomTaskIds = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            rightBottomTaskIds.add(parcel.readInt());
        }
    }

    @Override
@@ -154,11 +194,19 @@ public class SplitBounds implements Parcelable {
        parcel.writeFloat(leftTaskPercent);
        parcel.writeBoolean(appsStackedVertically);
        parcel.writeBoolean(initiatedFromSeascape);
        parcel.writeInt(leftTopTaskId);
        parcel.writeInt(rightBottomTaskId);
        parcel.writeFloat(dividerWidthPercent);
        parcel.writeFloat(dividerHeightPercent);
        parcel.writeInt(snapPosition);
        parcel.writeInt(leftTopTaskId);
        parcel.writeInt(rightBottomTaskId);
        parcel.writeInt(leftTopTaskIds.size());
        for (Integer id : leftTopTaskIds) {
            parcel.writeInt(id); // Write each Integer in the List
        }
        parcel.writeInt(rightBottomTaskIds.size());
        for (Integer id : rightBottomTaskIds) {
            parcel.writeInt(id); // Write each Integer in the List
        }
    }

    @Override
@@ -175,20 +223,23 @@ public class SplitBounds implements Parcelable {
        final SplitBounds other = (SplitBounds) obj;
        return Objects.equals(leftTopBounds, other.leftTopBounds)
                && Objects.equals(rightBottomBounds, other.rightBottomBounds)
                && leftTopTaskId == other.leftTopTaskId
                && rightBottomTaskId == other.rightBottomTaskId
                && leftTopTaskIds.equals(other.leftTopTaskIds)
                && rightBottomTaskIds.equals(other.rightBottomTaskIds)
                && snapPosition == other.snapPosition;
    }

    @Override
    public int hashCode() {
        return Objects.hash(leftTopBounds, rightBottomBounds, leftTopTaskId, rightBottomTaskId);
        return Objects.hash(leftTopBounds, rightBottomBounds,
                leftTopTaskId, rightBottomTaskId, leftTopTaskIds, rightBottomTaskIds);
    }

    @Override
    public String toString() {
        return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n"
                + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId +  "\n"
        return "LeftTop: " + leftTopBounds + " taskId: " + leftTopTaskId
                + ", taskIds: " + leftTopTaskIds + "\n"
                + "RightBottom: " + rightBottomBounds + " taskId: " + rightBottomTaskId
                + ", taskIds: " + rightBottomTaskIds +  "\n"
                + "Divider: " + visualDividerBounds + "\n"
                + "AppsVertical? " + appsStackedVertically + "\n"
                + "snapPosition: " + snapPosition;
+105 −94

File changed.

Preview size limit exceeded, changes collapsed.

+25 −0
Original line number Diff line number Diff line
@@ -57,9 +57,12 @@ import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * Base class that handle common task org. related for split-screen stages.
@@ -156,6 +159,16 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
        return taskInfo != null ? taskInfo.taskId : INVALID_TASK_ID;
    }

    /**
     * Returns all visible child task's ids.
     */
    List<Integer> getAllVisibleChildTaskIds() {
        return getAllChildTaskInfos(t -> t.isVisible
                && t.isVisibleRequested && t.taskId != INVALID_TASK_ID).stream()
                .map(runningTaskInfo -> runningTaskInfo.taskId)
                .collect(Collectors.toList());
    }

    /**
     * Returns the top activity uid for the top child task.
     */
@@ -216,6 +229,18 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
        return null;
    }

    private List<ActivityManager.RunningTaskInfo> getAllChildTaskInfos(
            Predicate<ActivityManager.RunningTaskInfo> predicate) {
        List<ActivityManager.RunningTaskInfo> matchingTasks = new ArrayList<>();
        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
            final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
            if (predicate.test(taskInfo)) {
                matchingTasks.add(taskInfo);
            }
        }
        return matchingTasks;
    }

    @Override
    @CallSuper
    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+42 −1
Original line number Diff line number Diff line
package com.android.wm.shell.recents;

import static android.app.ActivityTaskManager.INVALID_TASK_ID;

import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import android.graphics.Rect;
@@ -18,6 +21,8 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Collections;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class SplitBoundsTest extends ShellTestCase {
@@ -26,6 +31,7 @@ public class SplitBoundsTest extends ShellTestCase {
    private static final int DIVIDER_SIZE = 20;
    private static final int TASK_ID_1 = 4;
    private static final int TASK_ID_2 = 9;
    private static final int TASK_ID_ILLEGAL = INVALID_TASK_ID;

    // Bounds in screen space
    private final Rect mTopRect = new Rect();
@@ -94,4 +100,39 @@ public class SplitBoundsTest extends ShellTestCase {
        float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH;
        assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01);
    }

    @Test
    public void testIllegalTaskIds() {
        assertThrows(
                IllegalArgumentException.class,
                () -> new SplitBounds(mLeftRect, mRightRect,
                        TASK_ID_ILLEGAL, TASK_ID_2, SNAP_TO_2_50_50));
        assertThrows(
                IllegalArgumentException.class,
                () -> new SplitBounds(mLeftRect, mRightRect,
                        TASK_ID_1, TASK_ID_ILLEGAL, SNAP_TO_2_50_50));
        assertThrows(
                IllegalArgumentException.class,
                () -> new SplitBounds(mLeftRect, mRightRect,
                        TASK_ID_ILLEGAL, TASK_ID_ILLEGAL, SNAP_TO_2_50_50));
        assertThrows(
                IllegalArgumentException.class,
                () -> new SplitBounds(mLeftRect, mRightRect,
                        TASK_ID_1, TASK_ID_1, SNAP_TO_2_50_50));
        assertThrows(
                IllegalArgumentException.class,
                () -> new SplitBounds(mLeftRect, mRightRect,
                        TASK_ID_1, TASK_ID_2, Collections.emptyList(), Collections.emptyList(),
                        SNAP_TO_2_50_50));
        assertThrows(
                IllegalArgumentException.class,
                () -> new SplitBounds(mLeftRect, mRightRect,
                        TASK_ID_1, TASK_ID_2, Collections.singletonList(TASK_ID_1),
                        Collections.emptyList(), SNAP_TO_2_50_50));
        assertThrows(
                IllegalArgumentException.class,
                () -> new SplitBounds(mLeftRect, mRightRect,
                        TASK_ID_1, TASK_ID_2, Collections.emptyList(),
                        Collections.singletonList(TASK_ID_2), SNAP_TO_2_50_50));
    }
}
 No newline at end of file
+58 −0
Original line number Diff line number Diff line
@@ -54,6 +54,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

/**
@@ -187,4 +189,60 @@ public final class StageTaskListenerTests extends ShellTestCase {
        mStageTaskListener.deactivate(mWct);
        assertThat(mStageTaskListener.isActive()).isFalse();
    }

    @Test
    public void testGetAllVisibleChildTaskIds() {
        final ActivityManager.RunningTaskInfo taskVisible1 =
                new TestRunningTaskInfoBuilder()
                        .setTaskId(1)
                        .setVisible(true)
                        .setVisibleRequested(true)
                        .build();
        final ActivityManager.RunningTaskInfo taskInvisible2 =
                new TestRunningTaskInfoBuilder()
                        .setTaskId(2)
                        .setVisible(false)
                        .build();
        final ActivityManager.RunningTaskInfo taskVisible3 =
                new TestRunningTaskInfoBuilder()
                        .setTaskId(3)
                        .setVisible(true)
                        .setVisibleRequested(true)
                        .build();
        final ActivityManager.RunningTaskInfo taskVisible4 =
                new TestRunningTaskInfoBuilder()
                        .setTaskId(4)
                        .setVisible(true)
                        .setVisibleRequested(true)
                        .build();
        final ActivityManager.RunningTaskInfo taskInvisible5 =
                new TestRunningTaskInfoBuilder()
                        .setTaskId(5)
                        .setVisible(false)
                        .build();
        final List<Integer> visibleTaskIds = Arrays.asList(taskVisible1.taskId, taskVisible3.taskId,
                taskVisible4.taskId);

        mStageTaskListener.mChildrenTaskInfo.clear();
        assertThat(mStageTaskListener.mChildrenTaskInfo.size() == 0).isTrue();

        mStageTaskListener.mChildrenTaskInfo.put(taskVisible1.taskId, taskVisible1);
        mStageTaskListener.mChildrenTaskInfo.put(taskInvisible2.taskId, taskInvisible2);
        mStageTaskListener.mChildrenTaskInfo.put(taskVisible3.taskId, taskVisible3);
        mStageTaskListener.mChildrenTaskInfo.put(taskVisible4.taskId, taskVisible4);
        mStageTaskListener.mChildrenTaskInfo.put(taskInvisible5.taskId, taskInvisible5);

        final List<Integer> ids = mStageTaskListener.getAllVisibleChildTaskIds();
        assertThat(ids.size() == 3).isTrue();
        assertTrue("List should contain all visible taskIds",
                ids.containsAll(visibleTaskIds));
        assertFalse("List should not contain invisible taskId2",
                ids.contains(taskInvisible2.taskId));
        assertFalse("List should not contain invisible taskId5",
                ids.contains(taskInvisible5.taskId));

        // Clear the mChildrenTaskInfo.
        mStageTaskListener.mChildrenTaskInfo.clear();
        assertThat(mStageTaskListener.mChildrenTaskInfo.size() == 0).isTrue();
    }
}
Loading