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

Commit 68628867 authored by Gustav Sennton's avatar Gustav Sennton
Browse files

Add remoteTransition for moveTaskToFront(), and apply minimize reparent

Support new alt-tab animation behaviour:
1. allow passing a RemoteTransition through showDesktopApp(), and
2. reparent the leash of the minimize-change before applying that
RemoteTransition so the minimize window doesn't move in front of other
windows.

Test: manual, and DesktopWindowLimitRemoteHandlerTest
Bug: 349791584
Flag: com.android.window.flags.enable_desktop_app_launch_alttab_transitions
Change-Id: I1d98e77688133a517a2f343f8f0c4c5dd82e6f97
parent b16fb234
Loading
Loading
Loading
Loading
+56 −21
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentTasksController
@@ -521,7 +522,6 @@ class DesktopTasksController(
        wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)

        transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)

    }

    private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
@@ -563,10 +563,20 @@ class DesktopTasksController(
            )
    }

    /** Move a task to the front */
    fun moveTaskToFront(taskId: Int) {
    /**
     * Move a task to the front, using [remoteTransition].
     *
     * Note: beyond moving a task to the front, this method will minimize a task if we reach the
     * Desktop task limit, so [remoteTransition] should also handle any such minimize change.
     */
    @JvmOverloads
    fun moveTaskToFront(taskId: Int, remoteTransition: RemoteTransition? = null) {
        val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
        if (task == null) moveBackgroundTaskToFront(taskId) else moveTaskToFront(task)
        if (task == null) {
            moveBackgroundTaskToFront(taskId, remoteTransition)
        } else {
            moveTaskToFront(task, remoteTransition)
        }
    }

    /**
@@ -574,12 +584,10 @@ class DesktopTasksController(
     * desktop. If outside of desktop and want to launch a background task in desktop, use
     * [moveBackgroundTaskToDesktop] instead.
     */
    private fun moveBackgroundTaskToFront(taskId: Int) {
    private fun moveBackgroundTaskToFront(taskId: Int, remoteTransition: RemoteTransition?) {
        logV("moveBackgroundTaskToFront taskId=%s", taskId)
        val wct = WindowContainerTransaction()
        // TODO: b/342378842 - Instead of using default display, support multiple displays
        val taskToMinimize: RunningTaskInfo? =
            addAndGetMinimizeChangesIfNeeded(DEFAULT_DISPLAY, wct, taskId)
        val runOnTransit = immersiveTransitionHandler
            .exitImmersiveIfApplicable(wct, DEFAULT_DISPLAY)
        wct.startTask(
@@ -588,26 +596,56 @@ class DesktopTasksController(
                launchWindowingMode = WINDOWING_MODE_FREEFORM
            }.toBundle(),
        )
        val transition = transitions.startTransition(TRANSIT_OPEN, wct, null /* handler */)
        addPendingMinimizeTransition(transition, taskToMinimize)
        val transition = startLaunchTransition(TRANSIT_OPEN, wct, taskId, remoteTransition)
        runOnTransit?.invoke(transition)
    }

    /** Move a task to the front */
    fun moveTaskToFront(taskInfo: RunningTaskInfo) {
    /**
     * Move a task to the front, using [remoteTransition].
     *
     * Note: beyond moving a task to the front, this method will minimize a task if we reach the
     * Desktop task limit, so [remoteTransition] should also handle any such minimize change.
     */
    @JvmOverloads
    fun moveTaskToFront(taskInfo: RunningTaskInfo, remoteTransition: RemoteTransition? = null) {
        logV("moveTaskToFront taskId=%s", taskInfo.taskId)
        val wct = WindowContainerTransaction()
        wct.reorder(taskInfo.token, true /* onTop */, true /* includingParents */)
        val runOnTransit = immersiveTransitionHandler.exitImmersiveIfApplicable(
            wct, taskInfo.displayId)
        val taskToMinimize =
            addAndGetMinimizeChangesIfNeeded(taskInfo.displayId, wct, taskInfo.taskId)

        val transition = transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
        addPendingMinimizeTransition(transition, taskToMinimize)
        val transition =
            startLaunchTransition(TRANSIT_TO_FRONT, wct, taskInfo.taskId, remoteTransition)
        runOnTransit?.invoke(transition)
    }

    private fun startLaunchTransition(
        transitionType: Int,
        wct: WindowContainerTransaction,
        taskId: Int,
        remoteTransition: RemoteTransition?,
    ): IBinder {
        val taskToMinimize: RunningTaskInfo? =
            addAndGetMinimizeChangesIfNeeded(DEFAULT_DISPLAY, wct, taskId)
        if (remoteTransition == null) {
            val t = transitions.startTransition(transitionType, wct, null /* handler */)
            addPendingMinimizeTransition(t, taskToMinimize)
            return t
        }
        if (taskToMinimize == null) {
            val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
            val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
            remoteTransitionHandler.setTransition(t)
            return t
        }
        val remoteTransitionHandler =
            DesktopWindowLimitRemoteHandler(
                mainExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskToMinimize.taskId)
        val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
        remoteTransitionHandler.setTransition(t)
        addPendingMinimizeTransition(t, taskToMinimize)
        return t
    }

    /**
     * Move task to the next display.
     *
@@ -663,7 +701,6 @@ class DesktopTasksController(
        wct.reparent(task.token, displayAreaInfo.token, true /* onTop */)

        transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)

    }

    /** Moves a task in/out of full immersive state within the desktop. */
@@ -739,7 +776,6 @@ class DesktopTasksController(
        val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)

        toggleResizeDesktopTaskTransitionHandler.startTransition(wct)

    }

    private fun getMaximizeBounds(taskInfo: RunningTaskInfo, stableBounds: Rect): Rect {
@@ -851,7 +887,6 @@ class DesktopTasksController(
        val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)

        toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentDragBounds)

    }

    @VisibleForTesting
@@ -1999,9 +2034,9 @@ class DesktopTasksController(
            }
        }

        override fun showDesktopApp(taskId: Int) {
        override fun showDesktopApp(taskId: Int, remoteTransition: RemoteTransition?) {
            executeRemoteCallWithTaskPermission(controller, "showDesktopApp") { c ->
                c.moveTaskToFront(taskId)
                c.moveTaskToFront(taskId, remoteTransition)
            }
        }

+7 −2
Original line number Diff line number Diff line
@@ -35,8 +35,13 @@ interface IDesktopMode {
    /** @deprecated this is no longer supported. */
    void hideStashedDesktopApps(int displayId);

    /** Bring task with the given id to front */
    oneway void showDesktopApp(int taskId);
    /**
    * Bring task with the given id to front, using the given remote transition.
    *
    * <p> Note: beyond moving a task to the front, this method will minimize a task if we reach the
    * Desktop task limit, so {@code remoteTransition} should also handle any such minimize change.
    */
    oneway void showDesktopApp(int taskId, in @nullable RemoteTransition remoteTransition);

    /** Get count of visible desktop tasks on the given display */
    int getVisibleTaskCount(int displayId);
+100 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.desktopmode.minimize

import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.RemoteTransition
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionHandler

/**
 * Handles transitions that result in hitting the Desktop window limit, by performing some
 * preparation work and then delegating to [remoteTransition].
 *
 * This transition handler prepares the leash of the minimizing change referenced by
 * [taskIdToMinimize], and then delegates to [remoteTransition] to perform the actual transition.
 */
class DesktopWindowLimitRemoteHandler(
    mainExecutor: ShellExecutor,
    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
    remoteTransition: RemoteTransition,
    private val taskIdToMinimize: Int,
    ) : TransitionHandler {

    private val oneShotRemoteHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
    private var transition: IBinder? = null

    /** Sets the transition that will be handled - this must be called before [startAnimation]. */
    fun setTransition(transition: IBinder) {
        this.transition = transition
        oneShotRemoteHandler.setTransition(transition)
    }

    override fun handleRequest(
        transition: IBinder,
        request: TransitionRequestInfo
    ): WindowContainerTransaction? {
        this.transition = transition
        return oneShotRemoteHandler.handleRequest(transition, request)
    }

    override fun startAnimation(
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction,
        finishCallback: Transitions.TransitionFinishCallback
    ): Boolean {
        if (transition != this.transition) return false
        val minimizeChange = findMinimizeChange(info, taskIdToMinimize) ?: return false
        // Reparent the minimize change back to the root task display area, so the minimizing window
        // isn't shown in front of other windows. We do this here in Shell since Launcher doesn't
        // have access to RootTaskDisplayAreaOrganizer.
        applyMinimizeChangeReparenting(info, minimizeChange, startTransaction)
        return oneShotRemoteHandler.startAnimation(
            transition, info, startTransaction, finishTransaction, finishCallback)
    }

    private fun applyMinimizeChangeReparenting(
        info: TransitionInfo,
        minimizeChange: Change,
        startTransaction: SurfaceControl.Transaction,
    ) {
        val taskInfo = minimizeChange.taskInfo ?: return
        if (taskInfo.isFreeform && TransitionUtil.isOpeningMode(info.type)) {
            rootTaskDisplayAreaOrganizer.reparentToDisplayArea(
                taskInfo.displayId, minimizeChange.leash, startTransaction)
        }
    }

    private fun findMinimizeChange(
        info: TransitionInfo,
        taskIdToMinimize: Int,
    ): Change? =
        info.changes.find { change ->
            change.taskInfo?.taskId == taskIdToMinimize && change.mode == TRANSIT_TO_BACK }
}
+39 −8
Original line number Diff line number Diff line
@@ -97,6 +97,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreef
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.draganddrop.DragAndDropController
@@ -122,6 +123,7 @@ import java.util.function.Consumer
import java.util.Optional
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlin.test.assertIs
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlinx.coroutines.CoroutineScope
@@ -1338,7 +1340,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
    val task1 = setUpFreeformTask()
    setUpFreeformTask()

    controller.moveTaskToFront(task1)
    controller.moveTaskToFront(task1, remoteTransition = null)

    val wct = getLatestWct(type = TRANSIT_TO_FRONT)
    assertThat(wct.hierarchyOps).hasSize(1)
@@ -1350,7 +1352,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
    setUpHomeTask()
    val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }

    controller.moveTaskToFront(freeformTasks[0])
    controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)

    val wct = getLatestWct(type = TRANSIT_TO_FRONT)
    assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
@@ -1358,12 +1360,41 @@ class DesktopTasksControllerTest : ShellTestCase() {
    wct.assertReorderAt(1, freeformTasks[1], toTop = false)
  }

  @Test
  fun moveTaskToFront_remoteTransition_usesOneshotHandler() {
    setUpHomeTask()
    val freeformTasks = List(MAX_TASK_LIMIT) { setUpFreeformTask() }
    val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
    whenever(
      transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())
    ).thenReturn(Binder())

    controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))

    assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
  }

  @Test
  fun moveTaskToFront_bringsTasksOverLimit_remoteTransition_usesWindowLimitHandler() {
    setUpHomeTask()
    val freeformTasks = List(MAX_TASK_LIMIT + 1) { setUpFreeformTask() }
    val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
    whenever(
      transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())
    ).thenReturn(Binder())

    controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))

    assertThat(transitionHandlerArgCaptor.value)
      .isInstanceOf(DesktopWindowLimitRemoteHandler::class.java)
  }

  @Test
  fun moveTaskToFront_backgroundTask_launchesTask() {
    val task = createTaskInfo(1)
    whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)

    controller.moveTaskToFront(task.taskId)
    controller.moveTaskToFront(task.taskId, remoteTransition = null)

    val wct = getLatestWct(type = TRANSIT_OPEN)
    assertThat(wct.hierarchyOps).hasSize(1)
@@ -1376,12 +1407,12 @@ class DesktopTasksControllerTest : ShellTestCase() {
    val task = createTaskInfo(1001)
    whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)

    controller.moveTaskToFront(task.taskId)
    controller.moveTaskToFront(task.taskId, remoteTransition = null)

    val wct = getLatestWct(type = TRANSIT_OPEN)
    assertThat(wct.hierarchyOps.size).isEqualTo(2) // launch + minimize
    wct.assertReorderAt(0, freeformTasks[0], toTop = false)
    wct.assertLaunchTaskAt(1, task.taskId, WINDOWING_MODE_FREEFORM)
    wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
    wct.assertReorderAt(1, freeformTasks[0], toTop = false)
  }

  @Test
@@ -3319,7 +3350,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
      .exitImmersiveIfApplicable(any(), eq(task.displayId))).thenReturn(runOnStartTransit)
    whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)

    controller.moveTaskToFront(task.taskId)
    controller.moveTaskToFront(task.taskId, remoteTransition = null)

    verify(mockDesktopFullImmersiveTransitionHandler)
      .exitImmersiveIfApplicable(any(), eq(task.displayId))
@@ -3335,7 +3366,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
      .exitImmersiveIfApplicable(any(), eq(task.displayId))).thenReturn(runOnStartTransit)
    whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)

    controller.moveTaskToFront(task.taskId)
    controller.moveTaskToFront(task.taskId, remoteTransition = null)

    verify(mockDesktopFullImmersiveTransitionHandler)
      .exitImmersiveIfApplicable(any(), eq(task.displayId))
+167 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.desktopmode.minimize

import android.app.ActivityManager
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.os.Binder
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.IRemoteTransition
import android.window.RemoteTransition
import androidx.test.filters.SmallTest
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidTestingRunner::class)
class DesktopWindowLimitRemoteHandlerTest {

    private val shellExecutor = TestShellExecutor()
    private val transition: IBinder = Binder()

    private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
    private val remoteTransition = mock<RemoteTransition>()
    private val iRemoteTransition = mock<IRemoteTransition>()
    private val startT = mock<Transaction>()
    private val finishT = mock<Transaction>()
    private val finishCallback = mock<Transitions.TransitionFinishCallback>()

    @Before
    fun setUp() {
        whenever(remoteTransition.remoteTransition).thenReturn(iRemoteTransition)
        whenever(iRemoteTransition.asBinder()).thenReturn(mock(IBinder::class.java))
    }

    private fun createRemoteHandler(taskIdToMinimize: Int) =
        DesktopWindowLimitRemoteHandler(
            shellExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskIdToMinimize)

    @Test
    fun startAnimation_dontSetTransition_returnsFalse() {
        val minimizeTask = createDesktopTask()
        val remoteHandler = createRemoteHandler(taskIdToMinimize = minimizeTask.taskId)

        assertThat(remoteHandler.startAnimation(transition,
            createMinimizeTransitionInfo(minimizeTask), startT, finishT, finishCallback)
        ).isFalse()
    }

    @Test
    fun startAnimation_noMinimizeChange_returnsFalse() {
        val remoteHandler = createRemoteHandler(taskIdToMinimize = 1)
        remoteHandler.setTransition(transition)
        val info = createToFrontTransitionInfo()

        assertThat(
            remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)
        ).isFalse()
    }

    @Test
    fun startAnimation_correctTransition_returnsTrue() {
        val minimizeTask = createDesktopTask()
        val remoteHandler = createRemoteHandler(taskIdToMinimize = minimizeTask.taskId)
        remoteHandler.setTransition(transition)
        val info = createMinimizeTransitionInfo(minimizeTask)

        assertThat(
            remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)
        ).isTrue()
    }

    @Test
    fun startAnimation_noMinimizeChange_doesNotReparentMinimizeChange() {
        val remoteHandler = createRemoteHandler(taskIdToMinimize = 1)
        remoteHandler.setTransition(transition)
        val info = createToFrontTransitionInfo()

        remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)

        verify(rootTaskDisplayAreaOrganizer, times(0))
            .reparentToDisplayArea(anyInt(), any(), any())
    }

    @Test
    fun startAnimation_hasMinimizeChange_reparentsMinimizeChange() {
        val minimizeTask = createDesktopTask()
        val remoteHandler = createRemoteHandler(taskIdToMinimize = minimizeTask.taskId)
        remoteHandler.setTransition(transition)
        val info = createMinimizeTransitionInfo(minimizeTask)

        remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)

        verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea(anyInt(), any(), any())
    }

    @Test
    fun startAnimation_noMinimizeChange_doesNotStartRemoteAnimation() {
        val minimizeTask = createDesktopTask()
        val remoteHandler = createRemoteHandler(taskIdToMinimize = minimizeTask.taskId)
        remoteHandler.setTransition(transition)
        val info = createToFrontTransitionInfo()

        remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)

        verify(iRemoteTransition, times(0)).startAnimation(any(), any(), any(), any())
    }

    @Test
    fun startAnimation_hasMinimizeChange_startsRemoteAnimation() {
        val minimizeTask = createDesktopTask()
        val remoteHandler = createRemoteHandler(taskIdToMinimize = minimizeTask.taskId)
        remoteHandler.setTransition(transition)
        val info = createMinimizeTransitionInfo(minimizeTask)

        remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)

        verify(iRemoteTransition).startAnimation(any(), any(), any(), any())
    }

    private fun createDesktopTask() =
        TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build()

    private fun createToFrontTransitionInfo() =
        TransitionInfoBuilder(TRANSIT_TO_FRONT)
            .addChange(TRANSIT_TO_FRONT,
                TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build())
            .build()

    private fun createMinimizeTransitionInfo(minimizeTask: ActivityManager.RunningTaskInfo) =
        TransitionInfoBuilder(TRANSIT_TO_FRONT)
            .addChange(TRANSIT_TO_FRONT,
                TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build())
            .addChange(TRANSIT_TO_BACK, minimizeTask)
            .build()
}