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

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

Merge "Reparent split root for split select in external display" into main

parents 431671f1 98a20f34
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.wm.shell.splitscreen

import android.window.WindowContainerToken
import android.window.WindowContainerTransaction

interface SplitMultiDisplayProvider {
    /**
@@ -26,4 +27,15 @@ interface SplitMultiDisplayProvider {
     * @return The {@link WindowContainerToken} associated with the display's root task.
     */
    fun getDisplayRootForDisplayId(displayId: Int): WindowContainerToken?

    /**
     * Prepares to reparent the split-screen root to another display if the target task
     * resides on a different display. This is used to move the entire split-screen container
     * to the display where the user interaction is occurring. It only adds the reparent
     * operation to the given {@code wct} without executing it.
     *
     * @param wct The transaction to add the reparent operation to.
     * @param displayId The ID of the target display.
     */
    fun prepareMovingSplitScreenRoot(wct: WindowContainerTransaction?, displayId: Int)
}
+46 −11
Original line number Diff line number Diff line
@@ -320,6 +320,41 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        return rootTaskInfo != null ? rootTaskInfo.token : null;
    }

    @Override
    public void prepareMovingSplitScreenRoot(WindowContainerTransaction wct, int displayId) {
        // If multi split pair is supported, each display will have a root task, we won't need to
        // move split screen root to another display to make split pair work.
        if (enableMultiDisplaySplit()) {
            return;
        }

        // Find the current split-screen root task info.
        RunningTaskInfo currentRootTaskInfo = null;
        for (int id : mSplitMultiDisplayHelper.getCachedOrSystemDisplayIds()) {
            final RunningTaskInfo rootTaskInfo =
                    mSplitMultiDisplayHelper.getDisplayRootTaskInfo(id);
            if (rootTaskInfo != null) {
                currentRootTaskInfo = rootTaskInfo;
                break;
            }
        }

        if (currentRootTaskInfo == null) {
            throw new IllegalStateException("Failed to find current split screen root task info.");
        }

        // If the task to be launched is on a different display than the current split-screen
        // root, reparent the entire split-screen root to the task's display.
        if (displayId != currentRootTaskInfo.displayId) {
            final DisplayAreaInfo targetDisplayAreaInfo =
                    mRootTDAOrganizer.getDisplayAreaInfo(displayId);
            if (targetDisplayAreaInfo != null) {
                wct.reparent(currentRootTaskInfo.token, targetDisplayAreaInfo.token,
                        true /* onTop */);
            }
        }
    }

    class SplitRequest {
        @SplitPosition
        int mActivatePosition;
@@ -867,7 +902,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        }
        // For now, the only CUJ that can use this is LaunchAdjacent while on non-default displays.
        if (enableNonDefaultDisplaySplit()) {
            prepareMovingSplitscreenRoot(wct, displayId);
            prepareMovingSplitScreenRoot(wct, displayId);
        }
        wct.sendPendingIntent(intent, fillInIntent, options);

@@ -982,6 +1017,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
            return;
        }

        if (DesktopExperienceFlags.ENABLE_NON_DEFAULT_DISPLAY_SPLIT_BUGFIX.isTrue()) {
            // if the flag is enabled, split select can be initiated from external display.
            // Reparent the root task to correct display if necessary.
            final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(taskId);

            if (taskInfo != null) {
                prepareMovingSplitScreenRoot(wct, taskInfo.displayId);
            }
        }

        setSideStagePosition(splitPosition, wct);
        options1 = options1 != null ? options1 : new Bundle();
        StageTaskListener stageForTask1;
@@ -2074,16 +2119,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        setRootForceTranslucent(false, wct);
    }

    private void prepareMovingSplitscreenRoot(WindowContainerTransaction wct, int displayId) {
        if (!enableMultiDisplaySplit()) {
            final DisplayAreaInfo displayAreaInfo = mRootTDAOrganizer.getDisplayAreaInfo(displayId);
            final WindowContainerToken token = getDisplayRootForDisplayId(DEFAULT_DISPLAY);
            if (token != null && displayAreaInfo != null) {
                wct.reparent(token, displayAreaInfo.token, true /* onTop */);
            }
        }
    }

    void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen");
        mSplitLayout.updateStateWithCurrentPosition();
+104 −0
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -124,6 +125,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
@@ -671,6 +673,108 @@ public class StageCoordinatorTests extends ShellTestCase {
        assertThat(op.getDisallowOverrideBoundsForChildren()).isTrue();
    }

    @Test
    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT)
    public void moveSplitScreenRoot_whenFlagEnabled_doesNothing() {
        SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class);
        mStageCoordinator.mSplitMultiDisplayHelper = mockHelper;

        mStageCoordinator.prepareMovingSplitScreenRoot(mWct, DEFAULT_DISPLAY + 1);

        verify(mockHelper, never()).getCachedOrSystemDisplayIds();
        verify(mRootTDAOrganizer, never()).getDisplayAreaInfo(anyInt());
        verify(mWct, never()).reparent(any(), any(), anyBoolean());
    }

    @Test(expected = IllegalStateException.class)
    @DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT)
    public void moveSplitScreenRoot_whenRootNotFound_throwsException() {
        SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class);
        mStageCoordinator.mSplitMultiDisplayHelper = mockHelper;
        when(mockHelper.getCachedOrSystemDisplayIds()).thenReturn(
                new ArrayList<>(List.of(DEFAULT_DISPLAY)));
        when(mockHelper.getDisplayRootTaskInfo(anyInt())).thenReturn(null);

        mStageCoordinator.prepareMovingSplitScreenRoot(mWct, DEFAULT_DISPLAY + 1);
    }

    @Test
    @DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT)
    public void moveSplitScreenRoot_whenTargetIsSameDisplay_doesNothing() {
        SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class);
        mStageCoordinator.mSplitMultiDisplayHelper = mockHelper;
        final int targetDisplayId = DEFAULT_DISPLAY;
        ActivityManager.RunningTaskInfo currentRootTaskInfo = new TestRunningTaskInfoBuilder()
                .setDisplayId(targetDisplayId)
                .build();
        when(mockHelper.getCachedOrSystemDisplayIds()).thenReturn(
                new ArrayList<>(List.of(targetDisplayId)));
        when(mockHelper.getDisplayRootTaskInfo(targetDisplayId))
                .thenReturn(currentRootTaskInfo);

        mStageCoordinator.prepareMovingSplitScreenRoot(mWct, targetDisplayId);

        verify(mWct, never()).reparent(any(), any(), anyBoolean());
    }

    @Test
    @DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT)
    public void moveSplitScreenRoot_whenTargetIsDifferentDisplay_reparentsRoot() {
        SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class);
        mStageCoordinator.mSplitMultiDisplayHelper = mockHelper;
        final int currentDisplayId = DEFAULT_DISPLAY;
        final int targetDisplayId = DEFAULT_DISPLAY + 1;

        WindowContainerToken currentRootToken = mock(WindowContainerToken.class);
        when(mRootDisplayAreaOrganizer.getDisplayTokenForDisplay(anyInt()))
                .thenReturn(mock(WindowContainerToken.class));
        ActivityManager.RunningTaskInfo currentRootTaskInfo = new TestRunningTaskInfoBuilder()
                .setDisplayId(currentDisplayId)
                .setToken(currentRootToken)
                .build();
        when(mockHelper.getCachedOrSystemDisplayIds())
                .thenReturn(new ArrayList<>(List.of(currentDisplayId, targetDisplayId)));
        when(mockHelper.getDisplayRootTaskInfo(currentDisplayId))
                .thenReturn(currentRootTaskInfo);

        WindowContainerToken targetDisplayAreaToken = new MockToken().token();
        DisplayAreaInfo targetDisplayAreaInfo = new DisplayAreaInfo(targetDisplayAreaToken,
                targetDisplayId, 0);
        when(mRootTDAOrganizer.getDisplayAreaInfo(targetDisplayId))
                .thenReturn(targetDisplayAreaInfo);

        mStageCoordinator.prepareMovingSplitScreenRoot(mWct, targetDisplayId);

        verify(mWct).reparent(eq(currentRootToken), eq(targetDisplayAreaToken), eq(true));
    }

    @Test
    @DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT)
    public void moveSplitScreenRoot_whenTargetDisplayAreaNotFound_doesNothing() {
        SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class);
        mStageCoordinator.mSplitMultiDisplayHelper = mockHelper;

        final int currentDisplayId = DEFAULT_DISPLAY;
        final int targetDisplayId = DEFAULT_DISPLAY + 1;

        // Setup current root, but no target display area
        WindowContainerToken currentRootToken = mock(WindowContainerToken.class);
        ActivityManager.RunningTaskInfo currentRootTaskInfo = new TestRunningTaskInfoBuilder()
                .setDisplayId(currentDisplayId)
                .setToken(currentRootToken)
                .build();
        when(mockHelper.getCachedOrSystemDisplayIds())
                .thenReturn(new ArrayList<>(List.of(currentDisplayId, targetDisplayId)));
        when(mockHelper.getDisplayRootTaskInfo(currentDisplayId))
                .thenReturn(currentRootTaskInfo);

        when(mRootTDAOrganizer.getDisplayAreaInfo(targetDisplayId)).thenReturn(null);

        mStageCoordinator.prepareMovingSplitScreenRoot(mWct, targetDisplayId);

        verify(mWct, never()).reparent(any(), any(), anyBoolean());
    }

    private Transitions createTestTransitions() {
        ShellInit shellInit = new ShellInit(mMainExecutor);
        final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),