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

Commit bbbf9e3d authored by Shuming Hao's avatar Shuming Hao
Browse files

[1/N] Add one root task per display to support multi split

In StageCoordinator, start one root task for each display, and use the
SplitMultiDisplayHelper class to manager root tasks and root task leashes.

Flag: com.android.window.flags.enable_multi_display_split

Test: manual
Bug: 393217881
Change-Id: I3c0c2e5cfd7a04ff10b190aed92b37f6e16678f4
parent c7b882bc
Loading
Loading
Loading
Loading
+53 −2
Original line number Diff line number Diff line
@@ -15,10 +15,15 @@
 */

package com.android.wm.shell.splitscreen

import android.app.ActivityManager
import android.hardware.display.DisplayManager
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.window.TransitionInfo
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.common.split.SplitLayout
import com.android.wm.shell.protolog.ShellProtoLogGroup

@@ -94,7 +99,8 @@ class SplitMultiDisplayHelper(private val displayManager: DisplayManager) {
     * @return The root task info, or null if not found.
     */
    fun getDisplayRootTaskInfo(displayId: Int): ActivityManager.RunningTaskInfo? {
        return displayTaskMap[displayId]?.rootTaskInfo
        val targetDisplayId = if (Flags.enableMultiDisplaySplit()) displayId else DEFAULT_DISPLAY
        return displayTaskMap[targetDisplayId]?.rootTaskInfo
    }

    /**
@@ -107,7 +113,52 @@ class SplitMultiDisplayHelper(private val displayManager: DisplayManager) {
        displayId: Int,
        rootTaskInfo: ActivityManager.RunningTaskInfo?
    ) {
        val hierarchy = displayTaskMap.computeIfAbsent(displayId) { SplitTaskHierarchy() }
        val targetDisplayId = if (Flags.enableMultiDisplaySplit()) displayId else DEFAULT_DISPLAY
        val hierarchy = displayTaskMap.computeIfAbsent(targetDisplayId) { SplitTaskHierarchy() }
        hierarchy.rootTaskInfo = rootTaskInfo
    }

    fun getDisplayRootTaskLeash(displayId: Int): SurfaceControl? {
        val targetDisplayId = if (Flags.enableMultiDisplaySplit()) displayId else DEFAULT_DISPLAY
        return displayTaskMap[targetDisplayId]?.rootTaskLeash
    }

    fun setDisplayRootTaskLeash(
        displayId: Int,
        leash: SurfaceControl?
    ) {
        val targetDisplayId = if (Flags.enableMultiDisplaySplit()) displayId else DEFAULT_DISPLAY
        val hierarchy = displayTaskMap.computeIfAbsent(targetDisplayId) { SplitTaskHierarchy() }
        hierarchy.rootTaskLeash = leash
    }

    companion object {
        /**
         * Returns the display ID associated with the first change in the given [TransitionInfo].
         * It prioritize the end display ID of the change. If the end display ID is invalid, fall back
         * to the start display ID. If TransitionInfo has no changes, or if the first change has both
         * invalid end display ID and invalid start display ID, this method returns DEFAULT_DISPLAY.
         *
         * @param info the [TransitionInfo] containing transition changes
         * @return a valid display ID, or DEFAULT_DISPLAY as a fallback
         */
        @JvmStatic
        fun getTransitionDisplayId(info: TransitionInfo): Int {
            if (!Flags.enableMultiDisplaySplit()) (
                return DEFAULT_DISPLAY
            )

            if (info.changes.isEmpty()) {
                return DEFAULT_DISPLAY
            }
            // TODO: b/393217881 - take in a specific change instead of the first change for
            //  multi display split related tasks.
            val change: TransitionInfo.Change = info.changes.first()
            var displayId = change.endDisplayId
            if (displayId == Display.INVALID_DISPLAY) {
                displayId = change.startDisplayId
            }
            return if (displayId != Display.INVALID_DISPLAY) displayId else DEFAULT_DISPLAY
        }
    }
}
 No newline at end of file
+132 −56

File changed.

Preview size limit exceeded, changes collapsed.

+7 −4
Original line number Diff line number Diff line
@@ -80,14 +80,17 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
    @StageType private final int mId;
    /** Callback interface for listening to changes in a split-screen stage. */
    public interface StageListenerCallbacks {
        void onRootTaskAppeared();
        /** Called when the root task on current display appears. */
        void onRootTaskAppeared(ActivityManager.RunningTaskInfo taskInfo);

        void onStageVisibilityChanged(StageTaskListener stageTaskListener);

        void onChildTaskStatusChanged(StageTaskListener stage, int taskId, boolean present,
                boolean visible);

        void onRootTaskVanished();

        /** Called when the root task on current display vanishes. */
        void onRootTaskVanished(ActivityManager.RunningTaskInfo taskInfo);

        void onNoLongerSupportMultiWindow(StageTaskListener stageTaskListener,
                ActivityManager.RunningTaskInfo taskInfo);
@@ -220,7 +223,7 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
                    mRootTaskInfo.configuration,
                    mIconProvider);
            mHasRootTask = true;
            mCallbacks.onRootTaskAppeared();
            mCallbacks.onRootTaskAppeared(taskInfo);
            if (mVisible != mRootTaskInfo.isVisible) {
                mVisible = mRootTaskInfo.isVisible;
                mCallbacks.onStageVisibilityChanged(this);
@@ -281,7 +284,7 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
            mHasRootTask = false;
            mVisible = false;
            mHasChildren = false;
            mCallbacks.onRootTaskVanished();
            mCallbacks.onRootTaskVanished(taskInfo);
            mRootTaskInfo = null;
            mRootLeash = null;
            mSyncQueue.runInSync(t -> {
+3 −0
Original line number Diff line number Diff line
@@ -21,9 +21,11 @@ import android.hardware.display.DisplayManager
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags.enableMultiDisplaySplit
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.split.SplitLayout
import com.google.common.truth.Truth.assertThat
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -52,6 +54,7 @@ class SplitMultiDisplayHelperTests : ShellTestCase() {

    @Before
    fun setUp() {
        assumeTrue(enableMultiDisplaySplit())
        MockitoAnnotations.initMocks(this)
        mockDisplay1 = mockDisplayManager.getDisplay(Display.DEFAULT_DISPLAY) ?: mock(Display::class.java)
        mockDisplay2 = mockDisplayManager.getDisplay(Display.DEFAULT_DISPLAY + 1) ?: mock(Display::class.java)
+1 −2
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.app.ActivityManager;
import android.os.SystemProperties;
import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;

@@ -116,7 +115,7 @@ public final class StageTaskListenerTests extends ShellTestCase {
    @Test
    public void testRootTaskAppeared() {
        assertThat(mStageTaskListener.mRootTaskInfo.taskId).isEqualTo(mRootTask.taskId);
        verify(mCallbacks).onRootTaskAppeared();
        verify(mCallbacks).onRootTaskAppeared(mRootTask);
        verify(mCallbacks, never()).onStageVisibilityChanged(mStageTaskListener);
    }