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

Commit d3370c40 authored by Vinit Nayak's avatar Vinit Nayak
Browse files

Pass back split bounds and meta info with GroupedTaskInfo

* Compute and save a StagedSplitBounds object for every
pair to determine it's position, size, divider, etc.
* Moved GroupTask to Launcher
* Moved StagedSplitBounds from launcher to wm/shell
because sharing is caring.

Bug: 203006080
Change-Id: I8513e027f8c9606fbed1283013eafea2cf905f3f
parent 41d73671
Loading
Loading
Loading
Loading
+20 −2
Original line number Diff line number Diff line
@@ -41,10 +41,13 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.StagedSplitBounds;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Manages the recent task list from the system, caching it as necessary.
@@ -62,6 +65,13 @@ public class RecentTasksController implements TaskStackListenerCallback,
    // Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a
    // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1)
    private final SparseIntArray mSplitTasks = new SparseIntArray();
    /**
     * Maps taskId to {@link StagedSplitBounds} for both taskIDs.
     * Meaning there will be two taskId integers mapping to the same object.
     * If there's any ordering to the pairing than we can probably just get away with only one
     * taskID mapping to it, leaving both for consistency with {@link #mSplitTasks} for now.
     */
    private final Map<Integer, StagedSplitBounds> mTaskSplitBoundsMap = new HashMap<>();

    /**
     * Creates {@link RecentTasksController}, returns {@code null} if the feature is not
@@ -97,15 +107,20 @@ public class RecentTasksController implements TaskStackListenerCallback,
    /**
     * Adds a split pair. This call does not validate the taskIds, only that they are not the same.
     */
    public void addSplitPair(int taskId1, int taskId2) {
    public void addSplitPair(int taskId1, int taskId2, StagedSplitBounds splitBounds) {
        if (taskId1 == taskId2) {
            return;
        }
        // Remove any previous pairs
        removeSplitPair(taskId1);
        removeSplitPair(taskId2);
        mTaskSplitBoundsMap.remove(taskId1);
        mTaskSplitBoundsMap.remove(taskId2);

        mSplitTasks.put(taskId1, taskId2);
        mSplitTasks.put(taskId2, taskId1);
        mTaskSplitBoundsMap.put(taskId1, splitBounds);
        mTaskSplitBoundsMap.put(taskId2, splitBounds);
    }

    /**
@@ -116,6 +131,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
        if (pairedTaskId != INVALID_TASK_ID) {
            mSplitTasks.delete(taskId);
            mSplitTasks.delete(pairedTaskId);
            mTaskSplitBoundsMap.remove(taskId);
            mTaskSplitBoundsMap.remove(pairedTaskId);
        }
    }

@@ -203,7 +220,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
            if (pairedTaskId != INVALID_TASK_ID) {
                final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
                rawMapping.remove(pairedTaskId);
                recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo));
                recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo,
                        mTaskSplitBoundsMap.get(pairedTaskId)));
            } else {
                recentTasks.add(new GroupedRecentTaskInfo(taskInfo));
            }
+16 −1
Original line number Diff line number Diff line
@@ -96,6 +96,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.StagedSplitBounds;

import java.io.PrintWriter;
import java.util.ArrayList;
@@ -696,11 +697,25 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        }

        mRecentTasks.ifPresent(recentTasks -> {
            Rect topLeftBounds = mSplitLayout.getBounds1();
            Rect bottomRightBounds = mSplitLayout.getBounds2();
            int mainStageTopTaskId = mMainStage.getTopVisibleChildTaskId();
            int sideStageTopTaskId = mSideStage.getTopVisibleChildTaskId();
            boolean sideStageTopLeft = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
            int leftTopTaskId;
            int rightBottomTaskId;
            if (sideStageTopLeft) {
                leftTopTaskId = sideStageTopTaskId;
                rightBottomTaskId = mainStageTopTaskId;
            } else {
                leftTopTaskId = mainStageTopTaskId;
                rightBottomTaskId = sideStageTopTaskId;
            }
            StagedSplitBounds splitBounds = new StagedSplitBounds(topLeftBounds, bottomRightBounds,
                    leftTopTaskId, rightBottomTaskId);
            if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
                // Update the pair for the top tasks
                recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId);
                recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds);
            }
        });
    }
+13 −3
Original line number Diff line number Diff line
@@ -30,25 +30,34 @@ import androidx.annotation.Nullable;
public class GroupedRecentTaskInfo implements Parcelable {
    public @NonNull ActivityManager.RecentTaskInfo mTaskInfo1;
    public @Nullable ActivityManager.RecentTaskInfo mTaskInfo2;
    public @Nullable StagedSplitBounds mStagedSplitBounds;

    public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1) {
        this(task1, null);
        this(task1, null, null);
    }

    public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1,
            @Nullable ActivityManager.RecentTaskInfo task2) {
            @Nullable ActivityManager.RecentTaskInfo task2,
            @Nullable StagedSplitBounds stagedSplitBounds) {
        mTaskInfo1 = task1;
        mTaskInfo2 = task2;
        mStagedSplitBounds = stagedSplitBounds;
    }

    GroupedRecentTaskInfo(Parcel parcel) {
        mTaskInfo1 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR);
        mTaskInfo2 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR);
        mStagedSplitBounds = parcel.readTypedObject(StagedSplitBounds.CREATOR);
    }

    @Override
    public String toString() {
        return "Task1: " + getTaskInfo(mTaskInfo1) + ", Task2: " + getTaskInfo(mTaskInfo2);
        String taskString = "Task1: " + getTaskInfo(mTaskInfo1)
                + ", Task2: " + getTaskInfo(mTaskInfo2);
        if (mStagedSplitBounds != null) {
            taskString += ", SplitBounds: " + mStagedSplitBounds.toString();
        }
        return taskString;
    }

    private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) {
@@ -67,6 +76,7 @@ public class GroupedRecentTaskInfo implements Parcelable {
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeTypedObject(mTaskInfo1, flags);
        parcel.writeTypedObject(mTaskInfo2, flags);
        parcel.writeTypedObject(mStagedSplitBounds, flags);
    }

    @Override
+114 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.wm.shell.util;

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

/**
 * Container of various information needed to display split screen
 * tasks/leashes/etc in Launcher
 */
public class StagedSplitBounds implements Parcelable {
    public final Rect leftTopBounds;
    public final Rect rightBottomBounds;
    /** This rect represents the actual gap between the two apps */
    public final Rect visualDividerBounds;
    // This class is orientation-agnostic, so we compute both for later use
    public final float topTaskPercent;
    public final float leftTaskPercent;
    /**
     * If {@code true}, that means at the time of creation of this object, the
     * split-screened apps were vertically stacked. This is useful in scenarios like
     * rotation where the bounds won't change, but this variable can indicate what orientation
     * the bounds were originally in
     */
    public final boolean appsStackedVertically;
    public final int leftTopTaskId;
    public final int rightBottomTaskId;

    public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds,
            int leftTopTaskId, int rightBottomTaskId) {
        this.leftTopBounds = leftTopBounds;
        this.rightBottomBounds = rightBottomBounds;
        this.leftTopTaskId = leftTopTaskId;
        this.rightBottomTaskId = rightBottomTaskId;

        if (rightBottomBounds.top > leftTopBounds.top) {
            // vertical apps, horizontal divider
            this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom,
                    leftTopBounds.right, rightBottomBounds.top);
            appsStackedVertically = true;
        } else {
            // horizontal apps, vertical divider
            this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top,
                    rightBottomBounds.left, leftTopBounds.bottom);
            appsStackedVertically = false;
        }

        leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
        topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
    }

    public StagedSplitBounds(Parcel parcel) {
        leftTopBounds = parcel.readTypedObject(Rect.CREATOR);
        rightBottomBounds = parcel.readTypedObject(Rect.CREATOR);
        visualDividerBounds = parcel.readTypedObject(Rect.CREATOR);
        topTaskPercent = parcel.readFloat();
        leftTaskPercent = parcel.readFloat();
        appsStackedVertically = parcel.readBoolean();
        leftTopTaskId = parcel.readInt();
        rightBottomTaskId = parcel.readInt();
    }

    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeTypedObject(leftTopBounds, flags);
        parcel.writeTypedObject(rightBottomBounds, flags);
        parcel.writeTypedObject(visualDividerBounds, flags);
        parcel.writeFloat(topTaskPercent);
        parcel.writeFloat(leftTaskPercent);
        parcel.writeBoolean(appsStackedVertically);
        parcel.writeInt(leftTopTaskId);
        parcel.writeInt(rightBottomTaskId);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public String toString() {
        return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n"
                + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId +  "\n"
                + "Divider: " + visualDividerBounds + "\n"
                + "AppsVertical? " + appsStackedVertically;
    }

    public static final Creator<StagedSplitBounds> CREATOR = new Creator<StagedSplitBounds>() {
        @Override
        public StagedSplitBounds createFromParcel(Parcel in) {
            return new StagedSplitBounds(in);
        }

        @Override
        public StagedSplitBounds[] newArray(int size) {
            return new StagedSplitBounds[size];
        }
    };
}
+25 −6
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
@@ -31,10 +33,9 @@ import static org.mockito.Mockito.verify;
import static java.lang.Integer.MAX_VALUE;

import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.window.TaskAppearedInfo;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -45,6 +46,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.StagedSplitBounds;

import org.junit.Before;
import org.junit.Test;
@@ -106,8 +108,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
        setRawList(t1, t2, t3, t4, t5, t6);

        // Mark a couple pairs [t2, t4], [t3, t5]
        mRecentTasksController.addSplitPair(t2.taskId, t4.taskId);
        mRecentTasksController.addSplitPair(t3.taskId, t5.taskId);
        StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 4);
        StagedSplitBounds pair2Bounds = new StagedSplitBounds(new Rect(), new Rect(), 3, 5);

        mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
        mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);

        ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
                MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -126,7 +131,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
        setRawList(t1, t2, t3);

        // Add a pair
        mRecentTasksController.addSplitPair(t2.taskId, t3.taskId);
        StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 3);
        mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds);
        reset(mRecentTasksController);

        // Remove one of the tasks and ensure the pair is removed
@@ -201,10 +207,23 @@ public class RecentTasksControllerTest extends ShellTestCase {
        int[] flattenedTaskIds = new int[recentTasks.size() * 2];
        for (int i = 0; i < recentTasks.size(); i++) {
            GroupedRecentTaskInfo pair = recentTasks.get(i);
            flattenedTaskIds[2 * i] = pair.mTaskInfo1.taskId;
            int taskId1 = pair.mTaskInfo1.taskId;
            flattenedTaskIds[2 * i] = taskId1;
            flattenedTaskIds[2 * i + 1] = pair.mTaskInfo2 != null
                    ? pair.mTaskInfo2.taskId
                    : -1;

            if (pair.mTaskInfo2 != null) {
                assertNotNull(pair.mStagedSplitBounds);
                int leftTopTaskId = pair.mStagedSplitBounds.leftTopTaskId;
                int bottomRightTaskId = pair.mStagedSplitBounds.rightBottomTaskId;
                // Unclear if pairs are ordered by split position, most likely not.
                assertTrue(leftTopTaskId == taskId1 || leftTopTaskId == pair.mTaskInfo2.taskId);
                assertTrue(bottomRightTaskId == taskId1
                        || bottomRightTaskId == pair.mTaskInfo2.taskId);
            } else {
                assertNull(pair.mStagedSplitBounds);
            }
        }
        assertTrue("Expected: " + Arrays.toString(expectedTaskIds)
                        + " Received: " + Arrays.toString(flattenedTaskIds),
Loading