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

Commit 160e43be authored by Jorge Gil's avatar Jorge Gil
Browse files

[23/N] Desks: Deactivate desk when moving to split-screen

Ensures that when moving a deskop window to split-screen its desk is
deactivated (future launches aren't in freeform).

On the split side, this requires applying the split-select WCT using a
shell transition, so that the transition can be tracked by the
DesksTransitionObserver using the transition token.

Flag: com.android.window.flags.enable_multiple_desktops_backend
Bug: 394268248
Test: enter desktop with 1 window, use the app header to move it to
split, verify the desk is deactivated as a result (new launches are not
in freeform and the repository dump shows the desk as deactivated)

Change-Id: Ied4bb547b7b3c25395d80d31aa1439a24321c890
parent d77b2209
Loading
Loading
Loading
Loading
+22 −12
Original line number Diff line number Diff line
@@ -2700,10 +2700,12 @@ class DesktopTasksController(
    /**
     * Adds split screen changes to a transaction. Note that bounds are not reset here due to
     * animation; see {@link onDesktopSplitSelectAnimComplete}
     *
     * TODO: b/394268248 - desk needs to be deactivated.
     */
    private fun addMoveToSplitChanges(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
    private fun addMoveToSplitChanges(
        wct: WindowContainerTransaction,
        taskInfo: RunningTaskInfo,
        deskId: Int?,
    ): RunOnTransitStart? {
        // This windowing mode is to get the transition animation started; once we complete
        // split select, we will change windowing mode to undefined and inherit from split stage.
        // Going to undefined here causes task to flicker to the top left.
@@ -2713,11 +2715,12 @@ class DesktopTasksController(
        // want it overridden in multi-window.
        wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())

        performDesktopExitCleanupIfNeeded(
        return performDesktopExitCleanupIfNeeded(
            taskId = taskInfo.taskId,
            displayId = taskInfo.displayId,
            deskId = deskId,
            wct = wct,
            forceToFullscreen = false,
            forceToFullscreen = true,
            shouldEndUpAtHome = false,
        )
    }
@@ -2949,14 +2952,21 @@ class DesktopTasksController(
                    }
                dragToDesktopTransitionHandler.cancelDragToDesktopTransition(cancelState)
            } else {
                val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
                logV("Split requested for task=%d in desk=%d", taskInfo.taskId, deskId)
                val wct = WindowContainerTransaction()
                addMoveToSplitChanges(wct, taskInfo)
                val runOnTransitStart = addMoveToSplitChanges(wct, taskInfo, deskId)
                val transition =
                    splitScreenController.requestEnterSplitSelect(
                        taskInfo,
                        wct,
                    if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT,
                        if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT
                        else SPLIT_POSITION_BOTTOM_OR_RIGHT,
                        taskInfo.configuration.windowConfiguration.bounds,
                    )
                if (transition != null) {
                    runOnTransitStart?.invoke(transition)
                }
            }
        }
    }
+6 −2
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -580,10 +581,13 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
     * @param wct transaction to apply if this is a valid request
     * @param splitPosition the split position this task should move to
     * @param taskBounds current freeform bounds of the task entering split
     *
     * @return the token of the transition that started as a result of entering split select.
     */
    public void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
    @Nullable
    public IBinder requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
            WindowContainerTransaction wct, int splitPosition, Rect taskBounds) {
        mStageCoordinator.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds);
        return mStageCoordinator.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds);
    }

    /**
+12 −2
Original line number Diff line number Diff line
@@ -122,6 +122,7 @@ import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.widget.Toast;
import android.window.DesktopExperienceFlags;
import android.window.DisplayAreaInfo;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
@@ -219,6 +220,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
    private final Context mContext;
    private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
    private final Set<SplitScreen.SplitSelectListener> mSelectListeners = new HashSet<>();
    private final Transitions mTransitions;
    private final DisplayController mDisplayController;
    private final DisplayImeController mDisplayImeController;
    private final DisplayInsetsController mDisplayInsetsController;
@@ -419,6 +421,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                    iconProvider,
                    mWindowDecorViewModel, STAGE_TYPE_SIDE);
        }
        mTransitions = transitions;
        mDisplayController = displayController;
        mDisplayImeController = displayImeController;
        mDisplayInsetsController = displayInsetsController;
@@ -455,6 +458,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        mTaskOrganizer = taskOrganizer;
        mMainStage = mainStage;
        mSideStage = sideStage;
        mTransitions = transitions;
        mDisplayController = displayController;
        mDisplayImeController = displayImeController;
        mDisplayInsetsController = displayInsetsController;
@@ -660,16 +664,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        return mLogger;
    }

    void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
    @Nullable
    IBinder requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
            WindowContainerTransaction wct, int splitPosition, Rect taskBounds) {
        boolean enteredSplitSelect = false;
        for (SplitScreen.SplitSelectListener listener : mSelectListeners) {
            enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo, splitPosition,
                    taskBounds);
        }
        if (enteredSplitSelect) {
        if (!enteredSplitSelect) {
            return null;
        }
        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
            mTaskOrganizer.applyTransaction(wct);
            return null;
        }
        return mTransitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null);
    }

    void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
+24 −1
Original line number Diff line number Diff line
@@ -5248,6 +5248,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        assertThat(wctArgument.firstValue.hierarchyOps).isEmpty()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun enterSplit_wasInDesk_deactivatesDesk() {
        val deskId = 5
        taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
        taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
        val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
        val transition = Binder()
        whenever(splitScreenController.requestEnterSplitSelect(eq(task), any(), any(), any()))
            .thenReturn(transition)

        controller.requestSplit(task, leftOrTop = false)

        verify(desksOrganizer).deactivateDesk(any(), eq(deskId))
        verify(desksTransitionsObserver)
            .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
    fun newWindow_fromFullscreenOpensInSplit() {
@@ -6601,6 +6619,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        bounds: Rect? = null,
        active: Boolean = true,
        background: Boolean = false,
        deskId: Int? = null,
    ): RunningTaskInfo {
        val task = createFreeformTask(displayId, bounds)
        val activityInfo = ActivityInfo()
@@ -6613,7 +6632,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        } else {
            whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
        }
        if (deskId != null) {
            taskRepository.addTaskToDesk(displayId, deskId, task.taskId, isVisible = active)
        } else {
            taskRepository.addTask(displayId, task.taskId, isVisible = active)
        }
        if (!background) {
            runningTasks.add(task)
        }
+73 −0
Original line number Diff line number Diff line
@@ -38,6 +38,8 @@ import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -48,6 +50,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;
@@ -58,6 +61,7 @@ import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -196,6 +200,7 @@ public class StageCoordinatorTests extends ShellTestCase {
        when(token.asBinder()).thenReturn(mBinder);
        when(mRunningTaskInfo.getToken()).thenReturn(token);
        when(mTaskOrganizer.getRunningTaskInfo(mTaskId)).thenReturn(mRunningTaskInfo);
        when(mTaskOrganizer.startNewTransition(anyInt(), any())).thenReturn(new Binder());
        when(mRootTDAOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(mDisplayAreaInfo);

        when(mSplitLayout.getTopLeftBounds()).thenReturn(mBounds1);
@@ -557,6 +562,60 @@ public class StageCoordinatorTests extends ShellTestCase {
        assertFalse(c != null && c.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    public void testRequestEnterSplit_didNotEnterSplitSelect_doesNotApplyTransaction() {
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        mStageCoordinator.registerSplitSelectListener(
                new TestSplitSelectListener(/* alwaysEnter = */ false));

        final IBinder transition = mStageCoordinator.requestEnterSplitSelect(mRunningTaskInfo, wct,
                SPLIT_POSITION_TOP_OR_LEFT, new Rect(0, 0, 100, 100));

        assertNull(transition);
        verify(mTaskOrganizer, never()).applyTransaction(wct);
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    public void testRequestEnterSplit_enteredSplitSelect_appliesTransaction() {
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        mStageCoordinator.registerSplitSelectListener(
                new TestSplitSelectListener(/* alwaysEnter = */ true));

        final IBinder transition = mStageCoordinator.requestEnterSplitSelect(mRunningTaskInfo, wct,
                SPLIT_POSITION_TOP_OR_LEFT, new Rect(0, 0, 100, 100));

        assertNull(transition);
        verify(mTaskOrganizer).applyTransaction(wct);
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    public void testRequestEnterSplit_didNotEnterSplitSelect_doesNotStartTransition() {
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        mStageCoordinator.registerSplitSelectListener(
                new TestSplitSelectListener(/* alwaysEnter = */ false));

        final IBinder transition = mStageCoordinator.requestEnterSplitSelect(mRunningTaskInfo, wct,
                SPLIT_POSITION_TOP_OR_LEFT, new Rect(0, 0, 100, 100));

        assertNull(transition);
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    public void testRequestEnterSplit_enteredSplitSelect_startsTransition() {
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        mStageCoordinator.registerSplitSelectListener(
                new TestSplitSelectListener(/* alwaysEnter = */ true));

        final IBinder transition = mStageCoordinator.requestEnterSplitSelect(mRunningTaskInfo, wct,
                SPLIT_POSITION_TOP_OR_LEFT, new Rect(0, 0, 100, 100));

        assertNotNull(transition);
    }

    private Transitions createTestTransitions() {
        ShellInit shellInit = new ShellInit(mMainExecutor);
        final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
@@ -566,4 +625,18 @@ public class StageCoordinatorTests extends ShellTestCase {
        shellInit.init();
        return t;
    }

    private static class TestSplitSelectListener implements SplitScreen.SplitSelectListener {
        private final boolean mAlwaysEnter;

        TestSplitSelectListener(boolean alwaysEnter) {
            mAlwaysEnter = alwaysEnter;
        }

        @Override
        public boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
                int splitPosition, Rect taskBounds) {
            return mAlwaysEnter;
        }
    }
}