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

Commit 979cdcc5 authored by Jorge Gil's avatar Jorge Gil
Browse files

Desks: Additional back-nav considerations

Handles the case where a back nav transition does not contain a trigger
task and thus the forced-minimization (incl. the reparent to the
minimization root) isn't handled by
DesktopTasksController#handleRequest. Instead,
DesktopBackNavTransitionObserver checks for to-back changes and applies
the reparenting if needed.

Close transitions don't need this treatment, since there is nothing to
reparent and will just be saved as repository minimized state.

Also makes a few changes to "in desktop" and "is desktop task" logic, to
account for cases where we might not be "in desktop" anymore (because
the back nav was on the last task that also deactivated the desk) and
cases where the desktop task may not be W_M_FREEFORM.

Flag: com.android.window.flags.enable_multiple_desktops_backend
Fix: 413024179
Test: back nav different apps (Gmail, Chrome, YT - since some generate
to-back vs close transitions) in different scenarios:
- When the app is the last task
- When the app is not the last task, and there are other tasks expanded
- When the app is not the last task, but other tasks are all minimized
Verify animation looks ok, and swiping up to overview doesn't show the
app in the desktop tile (w/o exploded view) nor as a fullscreen tile.
Test: to test non-trigger task cases - disable the back-nav handling
logic in DesktopTasksController first.

Change-Id: I95fdfbc63be77466677fc219a5ff4bddfce4f271
parent 1ff45114
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -23,9 +23,11 @@ import android.content.Context
import android.os.Handler
import android.view.Choreographer
import android.view.SurfaceControl.Transaction
import android.window.DesktopExperienceFlags
import android.window.TransitionInfo.Change
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.shared.animation.WindowAnimator.BoundsAnimationParams.AnimationBounds
import java.time.Duration

/** Creates minimization animation */
@@ -39,6 +41,16 @@ object MinimizeAnimator {
            endOffsetYDp = 12f,
            endScale = 0.97f,
            interpolator = Interpolators.STANDARD_ACCELERATE,
            animBounds = if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
                // In some cases, nav-back on the last desktop task may cause it to be reparented
                // into a fullscreen TDA before being minimized back into a desk by
                // [DesktopBackNavTransitionObserver]. The minimize animation would then occur when
                // the task is still fullscreen, which means it should use the start bounds for the
                // minimize animation.
                AnimationBounds.START
            } else {
                AnimationBounds.END
            }
        )

    /**
+12 −3
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import android.view.Choreographer
import android.view.SurfaceControl
import android.view.animation.Interpolator
import android.window.TransitionInfo
import com.android.wm.shell.shared.animation.WindowAnimator.BoundsAnimationParams.AnimationBounds.END
import com.android.wm.shell.shared.animation.WindowAnimator.BoundsAnimationParams.AnimationBounds.START

/** Creates animations that can be applied to windows/surfaces. */
object WindowAnimator {
@@ -38,7 +40,10 @@ object WindowAnimator {
        val startScale: Float = 1f,
        val endScale: Float = 1f,
        val interpolator: Interpolator,
    )
        val animBounds: AnimationBounds = END,
    ) {
        enum class AnimationBounds { START, END }
    }

    /**
     * Creates an animator to reposition and scale the bounds of the leash of the given change.
@@ -54,10 +59,14 @@ object WindowAnimator {
        change: TransitionInfo.Change,
        transaction: SurfaceControl.Transaction,
    ): ValueAnimator {
        val bounds = when (boundsAnimDef.animBounds) {
            START -> change.startAbsBounds
            END -> change.endAbsBounds
        }
        val startPos =
            getPosition(
                displayMetrics,
                change.endAbsBounds,
                bounds,
                boundsAnimDef.startScale,
                boundsAnimDef.startOffsetYDp,
            )
@@ -65,7 +74,7 @@ object WindowAnimator {
        val endPos =
            getPosition(
                displayMetrics,
                change.endAbsBounds,
                bounds,
                boundsAnimDef.endScale,
                boundsAnimDef.endOffsetYDp,
            )
+2 −0
Original line number Diff line number Diff line
@@ -1386,6 +1386,7 @@ public abstract class WMShellModule {
            Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
            Optional<BackAnimationController> backAnimationController,
            DesksOrganizer desksOrganizer,
            Transitions transitions,
            DesktopState desktopState,
            ShellInit shellInit) {
        return desktopUserRepositories.flatMap(
@@ -1396,6 +1397,7 @@ public abstract class WMShellModule {
                                        desktopMixedTransitionHandler.get(),
                                        backAnimationController.get(),
                                        desksOrganizer,
                                        transitions,
                                        desktopState,
                                        shellInit)));
    }
+164 −70
Original line number Diff line number Diff line
@@ -18,11 +18,14 @@ package com.android.wm.shell.desktopmode

import android.app.ActivityManager.RunningTaskInfo
import android.os.IBinder
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.transitTypeToString
import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition
@@ -31,6 +34,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.desktopmode.DesktopState
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions

/**
 * Class responsible for updating [DesktopRepository] with back navigation related changes. Also
@@ -42,6 +46,7 @@ class DesktopBackNavTransitionObserver(
    private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
    private val backAnimationController: BackAnimationController,
    private val desksOrganizer: DesksOrganizer,
    private val transitions: Transitions,
    desktopState: DesktopState,
    shellInit: ShellInit,
) {
@@ -75,79 +80,91 @@ class DesktopBackNavTransitionObserver(

            val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
            if (desktopRepository.isExitingDesktopTask(change)) {
                logD("removeTaskIfNeeded taskId=%d", taskInfo.taskId)
                desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId)
            }
        }
    }

    private fun handleBackNavigation(transition: IBinder, info: TransitionInfo) {
        // When default back navigation happens, transition type is TO_BACK and the change is
        // TO_BACK. Mark the task going to back as minimized.
        if (info.type == TRANSIT_TO_BACK) {
            for (change in info.changes) {
                val taskInfo = change.taskInfo
                if (taskInfo == null || taskInfo.taskId == -1) {
                    continue
                }
                val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
                val isInDesktop = desktopRepository.isAnyDeskActive(taskInfo.displayId)
                if (
                    isInDesktop &&
                        change.mode == TRANSIT_TO_BACK &&
                        desktopRepository.isDesktopTask(taskInfo)
                ) {
                    val isLastTask =
                        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
                            desktopRepository.hasOnlyOneVisibleTask(taskInfo.displayId)
                        } else {
                            desktopRepository.isOnlyVisibleTask(taskInfo.taskId, taskInfo.displayId)
                        }
                    logD(
                        "handleBackNavigation marking to-back taskId=%d as minimized",
                        taskInfo.taskId,
        val taskToMinimize = findTaskToMinimize(info) ?: return
        logD("handleBackNavigation taskToMinimize=%s", taskToMinimize)
        desktopUserRepositories
            .getProfile(taskToMinimize.taskInfo.userId)
            .minimizeTaskInDesk(
                displayId = taskToMinimize.displayId,
                deskId = taskToMinimize.deskId,
                taskId = taskToMinimize.taskId,
            )
                    desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
        desktopMixedTransitionHandler.addPendingMixedTransition(
            DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
                transition,
                            taskInfo.taskId,
                            isLastTask,
                taskToMinimize.taskId,
                taskToMinimize.isLastTask,
            )
        )
        if (taskToMinimize.shouldReparentToDesk) {
            // The task was reparented out of the desk. Move it back into the desk, but minimized.
            val wct = WindowContainerTransaction()
            desksOrganizer.moveTaskToDesk(
                wct = wct,
                deskId = taskToMinimize.deskId,
                task = taskToMinimize.taskInfo,
                minimized = true,
            )
            if (!wct.isEmpty) {
                transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
            }
        }
    }
        } else if (info.type == TRANSIT_CLOSE) {
            // In some cases app will be closing as a result of back navigation but we would like
            // to minimize. Mark the task closing as minimized.
            var hasWallpaperClosing = false
            var minimizingTask: Int? = null
            for (change in info.changes) {
                val taskInfo = change.taskInfo
                if (taskInfo == null || taskInfo.taskId == -1) continue

                if (
                    TransitionUtil.isClosingMode(change.mode) &&
                        DesktopWallpaperActivity.isWallpaperTask(taskInfo)
    private data class TaskToMinimize(
        val taskId: Int,
        val deskId: Int,
        val displayId: Int,
        val isLastTask: Boolean,
        val shouldReparentToDesk: Boolean,
    ) {
                    hasWallpaperClosing = true
        constructor(
            taskInfo: RunningTaskInfo,
            deskId: Int,
            isLastTask: Boolean,
            shouldReparentToDesk: Boolean,
        ) : this(taskInfo.taskId, deskId, taskInfo.displayId, isLastTask, shouldReparentToDesk) {
            this.taskInfo = taskInfo
        }

                if (change.mode == TRANSIT_CLOSE && minimizingTask == null) {
                    minimizingTask = getMinimizingTaskForClosingTransition(taskInfo)
                }
        lateinit var taskInfo: RunningTaskInfo
    }

            if (minimizingTask == null) return
            // If the transition has wallpaper closing, it means we are moving out of desktop.
            logD("handleBackNavigation marking close taskId=%d as minimized", minimizingTask)
            desktopMixedTransitionHandler.addPendingMixedTransition(
                DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
                    transition,
                    minimizingTask,
                    isLastTask = hasWallpaperClosing,
                )
            )
    private fun findTaskToMinimize(info: TransitionInfo): TaskToMinimize? {
        if (info.type != TRANSIT_TO_BACK && info.type != TRANSIT_CLOSE) return null
        val hasWallpaperClosing =
            info.taskChanges().any { change ->
                TransitionUtil.isClosingMode(change.mode) &&
                    DesktopWallpaperActivity.isWallpaperTask(checkNotNull(change.taskInfo))
            }
        for (change in info.taskChanges()) {
            val mode = change.mode
            when (info.type) {
                TRANSIT_TO_BACK -> {
                    val taskToMinimize = getMinimizingTaskForToBackTransition(change)
                    if (taskToMinimize != null) {
                        return taskToMinimize
                    }
                }
                TRANSIT_CLOSE -> {
                    if (mode != TRANSIT_CLOSE) continue
                    val taskToMinimize =
                        getMinimizingTaskForClosingTransition(change, hasWallpaperClosing)
                    if (taskToMinimize != null) {
                        return taskToMinimize
                    }
                }
                else -> error("Unsupported transition type: ${transitTypeToString(info.type)}")
            }
        }
        return null
    }

    /**
@@ -162,21 +179,87 @@ class DesktopBackNavTransitionObserver(
     * will be rare. E.g. triggering back navigation on an app that pops up a close dialog, and
     * closing it will minimize it here.
     */
    private fun getMinimizingTaskForClosingTransition(taskInfo: RunningTaskInfo): Int? {
        val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
        val isInDesktop = desktopRepository.isAnyDeskActive(taskInfo.displayId)
    private fun getMinimizingTaskForClosingTransition(
        change: TransitionInfo.Change,
        hasWallpaperClosing: Boolean,
    ): TaskToMinimize? {
        val taskInfo = change.taskInfo ?: return null
        if (taskInfo.taskId == -1) return null
        val repository = desktopUserRepositories.getProfile(taskInfo.userId)
        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            val deskId = repository.getActiveDeskId(taskInfo.displayId)
            if (
                deskId != null &&
                    repository.isDesktopTask(taskInfo) &&
                    backAnimationController.latestTriggerBackTask == taskInfo.taskId &&
                    !repository.isClosingTask(taskInfo.taskId)
            ) {
                return TaskToMinimize(
                    taskInfo = taskInfo,
                    deskId = deskId,
                    isLastTask = hasWallpaperClosing,
                    shouldReparentToDesk = false,
                )
            }
            return null
        }
        val deskId = repository.getDeskIdForTask(taskInfo.taskId)
        if (
            isInDesktop &&
                desktopRepository.isDesktopTask(taskInfo) &&
            deskId != null &&
                backAnimationController.latestTriggerBackTask == taskInfo.taskId &&
                !desktopRepository.isClosingTask(taskInfo.taskId)
                !repository.isClosingTask(taskInfo.taskId)
        ) {
            desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
            return taskInfo.taskId
            return TaskToMinimize(
                taskInfo = taskInfo,
                deskId = deskId,
                isLastTask = hasWallpaperClosing,
                shouldReparentToDesk = false,
            )
        }
        return null
    }

    /**
     * Given this a task in a to-back transition, a task is assumed to be closed by back navigation
     * if:
     * 1) Desktop mode is visible.
     * 2) It is a desktop task.
     * 3) Change mode is to-back.
     */
    private fun getMinimizingTaskForToBackTransition(
        change: TransitionInfo.Change
    ): TaskToMinimize? {
        val taskInfo = change.taskInfo ?: return null
        if (taskInfo.taskId == -1) return null
        if (change.mode != TRANSIT_TO_BACK) return null
        val repository = desktopUserRepositories.getProfile(taskInfo.userId)
        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            val deskId = repository.getActiveDeskId(taskInfo.displayId)
            if (deskId != null && repository.isDesktopTask(taskInfo)) {
                return TaskToMinimize(
                    taskInfo = taskInfo,
                    deskId = deskId,
                    isLastTask = repository.isLastTask(taskInfo, deskId),
                    shouldReparentToDesk = false,
                )
            }
            return null
        }
        val deskId = repository.getDeskIdForTask(taskInfo.taskId)
        if (deskId == null) {
            return null
        }
        return TaskToMinimize(
            taskInfo = taskInfo,
            deskId = deskId,
            isLastTask = repository.isLastTask(taskInfo, deskId),
            // Some back navigation transitions can result in the task being reparented out of its
            // original desk and into the TDA. Given we want this task to end up minimized in that
            // same desk, check here if this happened so that we can reparent as minimized.
            shouldReparentToDesk = !desksOrganizer.isMinimizedInDeskAtEnd(change),
        )
    }

    private fun DesktopRepository.isDesktopTask(task: RunningTaskInfo): Boolean =
        if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            isActiveTask(task.taskId)
@@ -193,6 +276,17 @@ class DesktopBackNavTransitionObserver(
        }
    }

    private fun DesktopRepository.isLastTask(taskInfo: RunningTaskInfo, deskId: Int): Boolean =
        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            hasOnlyOneVisibleTask(taskInfo.displayId)
        } else {
            isOnlyVisibleTaskInDesk(taskInfo.taskId, deskId)
        }

    private fun TransitionInfo.taskChanges(): List<TransitionInfo.Change> {
        return changes.filter { change -> change.taskInfo != null && change.taskInfo?.taskId != -1 }
    }

    private fun logD(msg: String, vararg arguments: Any?) {
        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
    }
+11 −1
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.os.IBinder
import android.os.SystemProperties
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.DesktopExperienceFlags
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
@@ -98,7 +99,16 @@ class DesktopMinimizationTransitionHandler(
        animations +=
            info.changes
                .filter {
                    checkChangeMode(it) && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
                    checkChangeMode(it) &&
                        (it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM ||
                            // Minimizing desktop tasks can be fullscreen too, such as
                            // in some back-nav cases where the task is reparented out
                            // into a touch-first TDA before being forcibly put back into
                            // a desk as a minimized task by
                            // [DesktopBackBavTransitionObserver].
                            // Also, fullscreen-in-desktop tasks for immersive or
                            // fullscreen app requests.
                            DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue)
                }
                .mapNotNull {
                    createMinimizeAnimation(it, finishTransaction, onAnimFinish, startAnimDelay)
Loading