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

Commit 3113ad48 authored by Gustav Sennton's avatar Gustav Sennton
Browse files

Support splitting task limit minimize Change into its own transition

Before this CL we would add minimize changes to existing app launch
transitions through DTC#handleRequest(). With this CL we add support in
DesktopTasksLimiter to use runOnIdle() (called from onTransitionReady())
to check whether to launch a new minimize transition.
The benefit of using runOnIdle() instead of handleRequest() is that in
runOnIdle() we have information about all the collected Changes in the
transition so we can make a more informed decision on whether to
minimize. This new approach supports edge-cases such as launching task
trampolines and opening a new tab in Chrome.

Also support adding a start-delay to task limit minimize animations
through the property
"persist.wm.debug.desktop_transitions.minimize.start_delay_ms"

Bug: 404549853
Bug: 356337427
Flag: com.android.window.flags.enable_desktop_task_limit_separate_transition
Test: DesktopTasksLimiterTest, DesktopMixedTransitionHandlerTest
Change-Id: I47c035c260aab9146988031f6c1b872ddd4369c6
parent 14358312
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.view.SurfaceControl.Transaction
import android.window.TransitionInfo.Change
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
import com.android.internal.jank.InteractionJankMonitor
import java.time.Duration

/** Creates minimization animation */
object MinimizeAnimator {
@@ -48,6 +49,7 @@ object MinimizeAnimator {
     * @param animationHandler the Handler that the animation is running on.
     */
    @JvmStatic
    @JvmOverloads
    fun create(
        context: Context,
        change: Change,
@@ -55,6 +57,7 @@ object MinimizeAnimator {
        onAnimFinish: (Animator) -> Unit,
        interactionJankMonitor: InteractionJankMonitor,
        animationHandler: Handler,
        startAnimDelay: Duration = Duration.ZERO,
    ): Animator {
        val boundsAnimator = WindowAnimator.createBoundsAnimator(
            context.resources.displayMetrics,
@@ -91,6 +94,7 @@ object MinimizeAnimator {
            }
        }
        return AnimatorSet().apply {
            startDelay = startAnimDelay.toMillis()
            playTogether(boundsAnimator, alphaAnimator)
            addListener(listener)
        }
+3 −1
Original line number Diff line number Diff line
@@ -938,7 +938,8 @@ public abstract class WMShellModule {
            ShellTaskOrganizer shellTaskOrganizer,
            DesksOrganizer desksOrganizer,
            DesktopConfig desktopConfig,
            DesktopState desktopState) {
            DesktopState desktopState,
            Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler) {
        if (!desktopState.canEnterDesktopMode()
                || !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isTrue()) {
            return Optional.empty();
@@ -950,6 +951,7 @@ public abstract class WMShellModule {
                        desktopUserRepositories,
                        shellTaskOrganizer,
                        desksOrganizer,
                        desktopMixedTransitionHandler.get(),
                        maxTaskLimit <= 0 ? null : maxTaskLimit));
    }

+28 −3
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.animation.Animator
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.os.Handler
import android.os.IBinder
import android.os.SystemProperties
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
@@ -29,10 +30,12 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.animation.MinimizeAnimator.create
import com.android.wm.shell.transition.Transitions
import java.time.Duration

/**
 * The [Transitions.TransitionHandler] that handles transitions for tasks that are:
@@ -63,7 +66,9 @@ class DesktopMinimizationTransitionHandler(
        finishCallback: Transitions.TransitionFinishCallback,
    ): Boolean {
        val shouldAnimate =
            TransitionUtil.isClosingType(info.type) || info.type == Transitions.TRANSIT_MINIMIZE
            TransitionUtil.isClosingType(info.type) ||
                info.type == Transitions.TRANSIT_MINIMIZE ||
                info.type == TRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE
        if (!shouldAnimate) return false

        val animations = mutableListOf<Animator>()
@@ -80,14 +85,24 @@ class DesktopMinimizationTransitionHandler(

        val checkChangeMode = { change: TransitionInfo.Change ->
            change.mode == info.type ||
                (info.type == Transitions.TRANSIT_MINIMIZE && change.mode == TRANSIT_TO_BACK)
                (info.type == Transitions.TRANSIT_MINIMIZE && change.mode == TRANSIT_TO_BACK) ||
                (info.type == TRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE &&
                    change.mode == TRANSIT_TO_BACK)
        }
        val startAnimDelay =
            if (info.type == TRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE) {
                TASK_LIMIT_ANIM_START_DELAY
            } else {
                Duration.ZERO
            }
        animations +=
            info.changes
                .filter {
                    checkChangeMode(it) && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
                }
                .mapNotNull { createMinimizeAnimation(it, finishTransaction, onAnimFinish) }
                .mapNotNull {
                    createMinimizeAnimation(it, finishTransaction, onAnimFinish, startAnimDelay)
                }
        if (animations.isEmpty()) return false
        animExecutor.execute { animations.forEach(Animator::start) }
        return true
@@ -97,6 +112,7 @@ class DesktopMinimizationTransitionHandler(
        change: TransitionInfo.Change,
        finishTransaction: Transaction,
        onAnimFinish: (Animator) -> Unit,
        startAnimDelay: Duration,
    ): Animator? {
        val t = Transaction()
        val sc = change.leash
@@ -117,6 +133,7 @@ class DesktopMinimizationTransitionHandler(
            onAnimFinish,
            InteractionJankMonitor.getInstance(),
            animHandler,
            startAnimDelay,
        )
    }

@@ -125,6 +142,14 @@ class DesktopMinimizationTransitionHandler(
            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
        }

        private val TASK_LIMIT_ANIM_START_DELAY =
            Duration.ofMillis(
                SystemProperties.getLong(
                    "persist.wm.debug.desktop_transitions.minimize.start_delay_ms",
                    /* def= */ 0,
                )
            )

        const val TAG = "DesktopMinimizationTransitionHandler"
    }
}
+15 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.view.SurfaceControl
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
@@ -34,6 +35,7 @@ import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE
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
@@ -97,6 +99,17 @@ class DesktopMixedTransitionHandler(
            }
    }

    /** Starts a task limit minimize transition for [taskId]. */
    fun startTaskLimitMinimizeTransition(wct: WindowContainerTransaction, taskId: Int): IBinder {
        return transitions
            .startTransition(TRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE, wct, /* handler= */ this)
            .also { transition ->
                pendingMixedTransitions.add(
                    PendingMixedTransition.Minimize(transition, taskId, isLastTask = false)
                )
            }
    }

    /** Delegates starting PiP transition to [FreeformTaskTransitionHandler]. */
    override fun startPipTransition(wct: WindowContainerTransaction?): IBinder =
        freeformTaskTransitionHandler.startPipTransition(wct)
@@ -324,6 +337,8 @@ class DesktopMixedTransitionHandler(
        val shouldAnimate =
            if (info.type == Transitions.TRANSIT_MINIMIZE) {
                DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX.isTrue
            } else if (info.type == TRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE) {
                DesktopExperienceFlags.ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION.isTrue
            } else {
                DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue
            }
+70 −8
Original line number Diff line number Diff line
@@ -44,10 +44,11 @@ import com.android.wm.shell.transition.Transitions.TransitionObserver
 * TODO(b/400634379): Separate two responsibilities of this class into two classes.
 */
class DesktopTasksLimiter(
    transitions: Transitions,
    private val transitions: Transitions,
    private val desktopUserRepositories: DesktopUserRepositories,
    private val shellTaskOrganizer: ShellTaskOrganizer,
    private val desksOrganizer: DesksOrganizer,
    private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
    private val maxTasksLimit: Int?,
) {
    private val minimizeTransitionObserver = MinimizeTransitionObserver()
@@ -71,6 +72,9 @@ class DesktopTasksLimiter(
        }
    }

    /** Describes a task launch that might trigger a task limit minimize transition. */
    data class LaunchDetails(val deskId: Int, val taskId: Int?)

    data class TaskDetails(
        val displayId: Int,
        val taskId: Int,
@@ -97,6 +101,7 @@ class DesktopTasksLimiter(

    // TODO(b/333018485): replace this observer when implementing the minimize-animation
    private inner class MinimizeTransitionObserver : TransitionObserver {
        private val pendingTaskLimitTransitionTokens = mutableMapOf<IBinder, LaunchDetails>()
        private val pendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>()
        private val activeTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>()
        private val pendingUnminimizeTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>()
@@ -106,6 +111,10 @@ class DesktopTasksLimiter(
            pendingTransitionTokensAndTasks[transition] = taskDetails
        }

        fun addPendingTaskLimitTransitionToken(transition: IBinder, details: LaunchDetails) {
            pendingTaskLimitTransitionTokens[transition] = details
        }

        fun addPendingUnminimizeTransitionToken(transition: IBinder, taskDetails: TaskDetails) {
            pendingUnminimizeTransitionTokensAndTasks[transition] = taskDetails
        }
@@ -127,10 +136,48 @@ class DesktopTasksLimiter(
            finishTransaction: SurfaceControl.Transaction,
        ) {
            val taskRepository = desktopUserRepositories.current
            handleTaskLimitTransitionReady(taskRepository, transition, info)
            handleMinimizeTransitionReady(taskRepository, transition, info)
            handleUnminimizeTransitionReady(transition)
        }

        /**
         * Handles [#onTransitionReady()] for transitions that might trigger task limit minimize.
         */
        private fun handleTaskLimitTransitionReady(
            taskRepository: DesktopRepository,
            transition: IBinder,
            info: TransitionInfo,
        ) {
            val launchDetails = pendingTaskLimitTransitionTokens.remove(transition) ?: return
            logV("handleTaskLimitTransitionReady, transition=$transition, info=$info")
            transitions.runOnIdle {
                val expandedTaskIds =
                    taskRepository.getExpandedTasksIdsInDeskOrdered(launchDetails.deskId)
                logV("runOnIdle, expandedTasks=$expandedTaskIds")
                val taskIdToMinimize =
                    getTaskIdToMinimize(expandedTaskIds, /* launchingNewIntent= */ false)
                if (taskIdToMinimize != null) {
                    triggerMinimizeTransition(launchDetails.deskId, taskIdToMinimize)
                }
            }
        }

        private fun triggerMinimizeTransition(deskId: Int, taskIdToMinimize: Int) {
            val task = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) ?: return
            logV("triggerMinimizeTransition, found running task -> start transition, %s", task)
            val wct = WindowContainerTransaction()
            addMinimizeChange(deskId, task, wct)
            val transition =
                desktopMixedTransitionHandler.startTaskLimitMinimizeTransition(wct, task.taskId)
            addPendingMinimizeChange(
                transition,
                task.displayId,
                task.taskId,
                MinimizeReason.TASK_LIMIT,
            )
        }

        private fun handleMinimizeTransitionReady(
            taskRepository: DesktopRepository,
            transition: IBinder,
@@ -270,16 +317,31 @@ class DesktopTasksLimiter(
            )
        taskIdToMinimize
            ?.let { shellTaskOrganizer.getRunningTaskInfo(it) }
            ?.let { task ->
                if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
                    wct.reorder(task.token, /* onTop= */ false)
                } else {
                    desksOrganizer.minimizeTask(wct, deskId, task)
                }
            }
            ?.let { task -> addMinimizeChange(deskId, task, wct) }
        return taskIdToMinimize
    }

    private fun addMinimizeChange(
        deskId: Int,
        task: ActivityManager.RunningTaskInfo,
        wct: WindowContainerTransaction,
    ) =
        if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            desksOrganizer.minimizeTask(wct, deskId, task)
        } else {
            wct.reorder(task.token, /* onTop= */ false)
        }

    /**
     * Add a pending transition to trigger a new minimize transition in case the pending transition
     * takes us over the task limit.
     */
    fun addPendingTaskLimitTransition(transition: IBinder, deskId: Int, taskId: Int?) =
        minimizeTransitionObserver.addPendingTaskLimitTransitionToken(
            transition,
            LaunchDetails(deskId, taskId),
        )

    /**
     * Add a pending minimize transition change to update the list of minimized apps once the
     * transition goes through.
Loading