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

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

Merge "Add mixed transition for to-front with immersive exit" into main

parents 8496b66a 874cbb14
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -703,6 +703,7 @@ public abstract class WMShellModule {
            Transitions transitions,
            KeyguardManager keyguardManager,
            ReturnToDragStartAnimator returnToDragStartAnimator,
            Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
            EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
            ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
            DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
@@ -736,6 +737,7 @@ public abstract class WMShellModule {
                transitions,
                keyguardManager,
                returnToDragStartAnimator,
                desktopMixedTransitionHandler.get(),
                enterDesktopTransitionHandler,
                exitDesktopTransitionHandler,
                desktopModeDragAndDropTransitionHandler,
@@ -960,6 +962,7 @@ public abstract class WMShellModule {
            @DynamicOverride DesktopRepository desktopRepository,
            FreeformTaskTransitionHandler freeformTaskTransitionHandler,
            CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
            Optional<DesktopImmersiveController> desktopImmersiveController,
            InteractionJankMonitor interactionJankMonitor,
            @ShellMainThread Handler handler) {
        if (!DesktopModeStatus.canEnterDesktopMode(context)) {
@@ -972,6 +975,7 @@ public abstract class WMShellModule {
                        desktopRepository,
                        freeformTaskTransitionHandler,
                        closeDesktopTaskTransitionHandler,
                        desktopImmersiveController.get(),
                        interactionJankMonitor,
                        handler));
    }
+42 −17
Original line number Diff line number Diff line
@@ -130,7 +130,8 @@ class DesktopImmersiveController(
        displayId: Int
    ) {
        if (!Flags.enableFullyImmersiveInDesktop()) return
        exitImmersiveIfApplicable(wct, displayId)?.invoke(transition)
        val result = exitImmersiveIfApplicable(wct, displayId)
        result.asExit()?.runOnTransitionStart?.invoke(transition)
    }

    /**
@@ -145,16 +146,23 @@ class DesktopImmersiveController(
        wct: WindowContainerTransaction,
        displayId: Int,
        excludeTaskId: Int? = null,
    ): ((IBinder) -> Unit)? {
        if (!Flags.enableFullyImmersiveInDesktop()) return null
        val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId) ?: return null
    ): ExitResult {
        if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit
        val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId)
            ?: return ExitResult.NoExit
        if (immersiveTask == excludeTaskId) {
            return null
            return ExitResult.NoExit
        }
        val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask) ?: return null
        val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask)
            ?: return ExitResult.NoExit
        logV("Appending immersive exit for task: $immersiveTask in display: $displayId")
        wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
        return { transition -> addPendingImmersiveExit(immersiveTask, displayId, transition) }
        return ExitResult.Exit(
            exitingTask = immersiveTask,
            runOnTransitionStart = { transition ->
                addPendingImmersiveExit(immersiveTask, displayId, transition)
            }
        )
    }

    /**
@@ -167,22 +175,25 @@ class DesktopImmersiveController(
    fun exitImmersiveIfApplicable(
        wct: WindowContainerTransaction,
        taskInfo: RunningTaskInfo
    ): ((IBinder) -> Unit)? {
        if (!Flags.enableFullyImmersiveInDesktop()) return null
    ): ExitResult {
        if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit
        if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
            // A full immersive task is being minimized, make sure the immersive state is broken
            // (i.e. resize back to max bounds).
            wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
            logV("Appending immersive exit for task: ${taskInfo.taskId}")
            return { transition ->
            return ExitResult.Exit(
                exitingTask = taskInfo.taskId,
                runOnTransitionStart = { transition ->
                    addPendingImmersiveExit(
                        taskId = taskInfo.taskId,
                        displayId = taskInfo.displayId,
                        transition = transition
                    )
                }
            )
        }
        return null
        return ExitResult.NoExit
    }


@@ -461,6 +472,20 @@ class DesktopImmersiveController(
        var transition: IBinder,
    )

    /** The result of an external exit request. */
    sealed class ExitResult {
        /** An immersive task exit (meaning, resize) was appended to the request. */
        data class Exit(
            val exitingTask: Int,
            val runOnTransitionStart: ((IBinder) -> Unit)
        ) : ExitResult()
        /** There was no exit appended to the request. */
        data object NoExit : ExitResult()

        /** Returns the result as an [Exit] or null if it isn't of that type. */
        fun asExit(): Exit? = if (this is Exit) this else null
    }

    private enum class Direction {
        ENTER, EXIT
    }
+202 −4
Original line number Diff line number Diff line
@@ -27,15 +27,18 @@ import android.window.DesktopModeFlags
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.transition.MixedTransitionHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback

/** The [Transitions.TransitionHandler] coordinates transition handlers in desktop windowing. */
class DesktopMixedTransitionHandler(
@@ -44,10 +47,14 @@ class DesktopMixedTransitionHandler(
    private val desktopRepository: DesktopRepository,
    private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler,
    private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
    private val desktopImmersiveController: DesktopImmersiveController,
    private val interactionJankMonitor: InteractionJankMonitor,
    @ShellMainThread private val handler: Handler,
) : MixedTransitionHandler, FreeformTaskTransitionStarter {

    @VisibleForTesting
    val pendingMixedTransitions = mutableListOf<PendingMixedTransition>()

    /** Delegates starting transition to [FreeformTaskTransitionHandler]. */
    override fun startWindowingModeTransition(
        targetWindowingMode: Int,
@@ -65,6 +72,40 @@ class DesktopMixedTransitionHandler(
        }
        requireNotNull(wct)
        return transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this)
            .also { transition ->
                pendingMixedTransitions.add(PendingMixedTransition.Close(transition))
            }
    }

    /**
     * Starts a launch transition for [taskId], with an optional [exitingImmersiveTask] if it was
     * included in the [wct] and is expected to be animated by this handler.
     */
    fun startLaunchTransition(
        @WindowManager.TransitionType transitionType: Int,
        wct: WindowContainerTransaction,
        taskId: Int,
        exitingImmersiveTask: Int? = null,
    ): IBinder {
        if (!Flags.enableFullyImmersiveInDesktop()) {
            return transitions.startTransition(transitionType, wct, /* handler= */ null)
        }
        if (exitingImmersiveTask == null) {
            logV("Starting mixed launch transition for task#%d", taskId)
        } else {
            logV(
                "Starting mixed launch transition for task#%d with immersive exit of task#%d",
                taskId, exitingImmersiveTask
            )
        }
        return transitions.startTransition(transitionType, wct, /* handler= */ this)
            .also { transition ->
                pendingMixedTransitions.add(PendingMixedTransition.Launch(
                    transition = transition,
                    launchingTask = taskId,
                    exitingImmersiveTask = exitingImmersiveTask
                ))
            }
    }

    /** Returns null, as it only handles transitions started from Shell. */
@@ -78,11 +119,43 @@ class DesktopMixedTransitionHandler(
        info: TransitionInfo,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction,
        finishCallback: Transitions.TransitionFinishCallback,
        finishCallback: TransitionFinishCallback,
    ): Boolean {
        val pending = pendingMixedTransitions.find { pending -> pending.transition == transition }
            ?: return false.also {
                logW("Should have pending desktop transition")
            }
        pendingMixedTransitions.remove(pending)
        logV("Animating pending mixed transition: %s", pending)
        return when (pending) {
            is PendingMixedTransition.Close -> animateCloseTransition(
                transition,
                info,
                startTransaction,
                finishTransaction,
                finishCallback
            )
            is PendingMixedTransition.Launch -> animateLaunchTransition(
                pending,
                transition,
                info,
                startTransaction,
                finishTransaction,
                finishCallback
            )
        }
    }

    private fun animateCloseTransition(
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction,
        finishCallback: TransitionFinishCallback,
    ): Boolean {
        val closeChange = findCloseDesktopTaskChange(info)
        if (closeChange == null) {
            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: Should have closing desktop task", TAG)
            logW("Should have closing desktop task")
            return false
        }
        if (isLastDesktopTask(closeChange)) {
@@ -106,6 +179,74 @@ class DesktopMixedTransitionHandler(
        )
    }

    private fun animateLaunchTransition(
        pending: PendingMixedTransition.Launch,
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction,
        finishCallback: TransitionFinishCallback,
    ): Boolean {
        // Check if there's also an immersive change during this launch.
        val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask ->
            findDesktopTaskChange(info, exitingTask)
        }
        val launchChange = findDesktopTaskChange(info, pending.launchingTask)
            ?: error("Should have pending launching task change")

        var subAnimationCount = -1
        var combinedWct: WindowContainerTransaction? = null
        val finishCb = TransitionFinishCallback { wct ->
            --subAnimationCount
            combinedWct = combinedWct.merge(wct)
            if (subAnimationCount > 0) return@TransitionFinishCallback
            finishCallback.onTransitionFinished(combinedWct)
        }

        logV(
            "Animating pending mixed launch transition task#%d immersiveExitTask#%s",
            launchChange.taskInfo!!.taskId, immersiveExitChange?.taskInfo?.taskId
        )
        if (immersiveExitChange != null) {
            subAnimationCount = 2
            // Animate the immersive exit change separately.
            info.changes.remove(immersiveExitChange)
            desktopImmersiveController.animateResizeChange(
                immersiveExitChange,
                startTransaction,
                finishTransaction,
                finishCb
            )
            // Let the leftover/default handler animate the remaining changes.
            return dispatchToLeftoverHandler(
                transition,
                info,
                startTransaction,
                finishTransaction,
                finishCb
            )
        }
        // There's nothing to animate separately, so let the left over handler animate
        // the entire transition.
        subAnimationCount = 1
        return dispatchToLeftoverHandler(
            transition,
            info,
            startTransaction,
            finishTransaction,
            finishCb
        )
    }

    override fun onTransitionConsumed(
        transition: IBinder,
        aborted: Boolean,
        finishTransaction: SurfaceControl.Transaction?
    ) {
        pendingMixedTransitions.removeAll { pending -> pending.transition == transition }
        super.onTransitionConsumed(transition, aborted, finishTransaction)
    }

    /**
     * Dispatch close desktop task animation to the default transition handlers. Allows delegating
     * it to Launcher to animate in sync with show Home transition.
@@ -126,14 +267,34 @@ class DesktopMixedTransitionHandler(
            CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE,
        )
        // Dispatch the last desktop task closing animation.
        return dispatchToLeftoverHandler(
            transition = transition,
            info = info,
            startTransaction = startTransaction,
            finishTransaction = finishTransaction,
            finishCallback = finishCallback,
            doOnFinishCallback = {
                // Finish the jank trace when closing the last window in desktop mode.
                interactionJankMonitor.end(CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE)
            }
        )
    }

    private fun dispatchToLeftoverHandler(
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction,
        finishCallback: TransitionFinishCallback,
        doOnFinishCallback: (() -> Unit)? = null,
    ): Boolean {
        return transitions.dispatchTransition(
            transition,
            info,
            startTransaction,
            finishTransaction,
            { wct ->
                // Finish the jank trace when closing the last window in desktop mode.
                interactionJankMonitor.end(CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE)
                doOnFinishCallback?.invoke()
                finishCallback.onTransitionFinished(wct)
            },
            /* skip= */ this
@@ -155,6 +316,43 @@ class DesktopMixedTransitionHandler(
        }
    }

    private fun findDesktopTaskChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? {
        return info.changes.firstOrNull { change -> change.taskInfo?.taskId == taskId }
    }

    private fun WindowContainerTransaction?.merge(
        wct: WindowContainerTransaction?
    ): WindowContainerTransaction? {
        if (wct == null) return this
        if (this == null) return wct
        return this.merge(wct)
    }

    /** A scheduled transition that will potentially be animated by more than one handler */
    sealed class PendingMixedTransition {
        abstract val transition: IBinder

        /** A task is closing. */
        data class Close(
            override val transition: IBinder,
        ) : PendingMixedTransition()

        /** A task is opening or moving to front. */
        data class Launch(
            override val transition: IBinder,
            val launchingTask: Int,
            val exitingImmersiveTask: Int?,
        ) : PendingMixedTransition()
    }

    private fun logV(msg: String, vararg arguments: Any?) {
        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
    }

    private fun logW(msg: String, vararg arguments: Any?) {
        ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
    }

    companion object {
        private const val TAG = "DesktopMixedTransitionHandler"
    }
+46 −30
Original line number Diff line number Diff line
@@ -87,6 +87,7 @@ import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
@@ -146,6 +147,7 @@ class DesktopTasksController(
    private val transitions: Transitions,
    private val keyguardManager: KeyguardManager,
    private val returnToDragStartAnimator: ReturnToDragStartAnimator,
    private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
    private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
    private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
    private val desktopModeDragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler,
@@ -379,7 +381,7 @@ class DesktopTasksController(
        // TODO(342378842): Instead of using default display, support multiple displays
        val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
            DEFAULT_DISPLAY, wct, taskId)
        val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
        val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
            wct = wct,
            displayId = DEFAULT_DISPLAY,
            excludeTaskId = taskId,
@@ -393,7 +395,7 @@ class DesktopTasksController(
        // TODO(343149901): Add DPI changes for task launch
        val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
        taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
        runOnTransit?.invoke(transition)
        exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
        return true
    }

@@ -410,7 +412,7 @@ class DesktopTasksController(
        }
        logV("moveRunningTaskToDesktop taskId=%d", task.taskId)
        exitSplitIfApplicable(wct, task)
        val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
        val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
            wct = wct,
            displayId = task.displayId,
            excludeTaskId = task.taskId,
@@ -422,7 +424,7 @@ class DesktopTasksController(

        val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
        taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
        runOnTransit?.invoke(transition)
        exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
    }

    /**
@@ -459,12 +461,12 @@ class DesktopTasksController(
        val taskIdToMinimize =
            bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
        addMoveToDesktopChanges(wct, taskInfo)
        val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
        val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
            wct, taskInfo.displayId)
        val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct)
        transition?.let {
            taskIdToMinimize?.let { taskId -> addPendingMinimizeTransition(it, taskId) }
            runOnTransit?.invoke(transition)
            exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
        }
    }

@@ -507,7 +509,8 @@ class DesktopTasksController(
                taskId
            )
        )
        return desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo)
        return desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo).asExit()
            ?.runOnTransitionStart
    }

    fun minimizeTask(taskInfo: RunningTaskInfo) {
@@ -520,7 +523,7 @@ class DesktopTasksController(
            removeWallpaperActivity(wct)
        }
        // Notify immersive handler as it might need to exit immersive state.
        val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo)
        val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo)

        wct.reorder(taskInfo.token, false)
        val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct)
@@ -531,7 +534,7 @@ class DesktopTasksController(
                taskId = taskId
            )
        }
        runOnTransit?.invoke(transition)
        exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
    }

    /** Move a task with given `taskId` to fullscreen */
@@ -627,7 +630,7 @@ class DesktopTasksController(
        logV("moveBackgroundTaskToFront taskId=%s", taskId)
        val wct = WindowContainerTransaction()
        // TODO: b/342378842 - Instead of using default display, support multiple displays
        val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
        val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
            wct = wct,
            displayId = DEFAULT_DISPLAY,
            excludeTaskId = taskId,
@@ -638,8 +641,13 @@ class DesktopTasksController(
                launchWindowingMode = WINDOWING_MODE_FREEFORM
            }.toBundle(),
        )
        val transition = startLaunchTransition(TRANSIT_OPEN, wct, taskId, remoteTransition)
        runOnTransit?.invoke(transition)
        val transition = startLaunchTransition(
            TRANSIT_OPEN,
            wct,
            taskId,
            remoteTransition = remoteTransition
        )
        exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
    }

    /**
@@ -658,32 +666,39 @@ class DesktopTasksController(
        }
        val wct = WindowContainerTransaction()
        wct.reorder(taskInfo.token, true /* onTop */, true /* includingParents */)
        val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
        val result = desktopImmersiveController.exitImmersiveIfApplicable(
            wct = wct,
            displayId = taskInfo.displayId,
            excludeTaskId = taskInfo.taskId,
        )
        val transition =
            startLaunchTransition(
                TRANSIT_TO_FRONT,
                wct,
                taskInfo.taskId,
                remoteTransition,
                taskInfo.displayId
        val exitResult = if (result is ExitResult.Exit) { result } else { null }
        val transition = startLaunchTransition(
            transitionType = TRANSIT_TO_FRONT,
            wct = wct,
            taskId = taskInfo.taskId,
            exitingImmersiveTask = exitResult?.exitingTask,
            remoteTransition = remoteTransition,
            displayId = taskInfo.displayId,
        )
        runOnTransit?.invoke(transition)
        exitResult?.runOnTransitionStart?.invoke(transition)
    }

    private fun startLaunchTransition(
        transitionType: Int,
        wct: WindowContainerTransaction,
        taskId: Int,
        remoteTransition: RemoteTransition?,
        exitingImmersiveTask: Int? = null,
        remoteTransition: RemoteTransition? = null,
        displayId: Int = DEFAULT_DISPLAY,
    ): IBinder {
        val taskIdToMinimize = addAndGetMinimizeChanges(displayId, wct, taskId)
        if (remoteTransition == null) {
            val t = transitions.startTransition(transitionType, wct, null /* handler */)
            val t = desktopMixedTransitionHandler.startLaunchTransition(
                transitionType = transitionType,
                wct = wct,
                taskId = taskId,
                exitingImmersiveTask = exitingImmersiveTask,
            )
            taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
            return t
        }
@@ -1373,14 +1388,15 @@ class DesktopTasksController(
            wct.startTask(requestedTaskId, options.toBundle())
            val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
                callingTask.displayId, wct, requestedTaskId)
            val runOnTransit = desktopImmersiveController.exitImmersiveIfApplicable(
            val exitResult = desktopImmersiveController
                .exitImmersiveIfApplicable(
                    wct = wct,
                    displayId = callingTask.displayId,
                    excludeTaskId = requestedTaskId,
                )
            val transition = transitions.startTransition(TRANSIT_OPEN, wct, null)
            taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
            runOnTransit?.invoke(transition)
            exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
        } else {
            val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
            splitScreenController.startTask(requestedTaskId, splitPosition,
+26 −11

File changed.

Preview size limit exceeded, changes collapsed.

Loading