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

Commit f2873f4b authored by Ivan Tkachenko's avatar Ivan Tkachenko
Browse files

Add flagged drag to desktop transition handler

Setup
- Converted `DragToDesktopTransitionHandler` to sealed class and added 2
  subclasses with default and flagged spring implementation.
- Flagged spring class will override/implement some of the transition handler
  behavior.
- `WMShellModule` provides default or flagged transition handler based
  on `enable_desktop_windowing_transitions` flag value

Flagged changes
- Spring transition handler changes the start drag to desktop
  transition layering and hides home leash on transition end
- It's also going to be used to update the drag to desktop animations in the following CL

Bug: 331163734
Test: atest WMShellUnitTests:DragToDesktopTransitionHandlerTest
Flag: com.android.window.flags.enable_desktop_windowing_transitions
Change-Id: Ia73e29163735c4feb4116fd9b1ad27ff4b247542
parent 6a4c1178
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -58,6 +59,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -68,6 +70,7 @@ import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator;
import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.GlobalDragListener;
@@ -603,9 +606,11 @@ public abstract class WMShellModule {
            Context context,
            Transitions transitions,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            Optional<DesktopTasksLimiter> desktopTasksLimiter,
            InteractionJankMonitor interactionJankMonitor) {
        return new DragToDesktopTransitionHandler(context, transitions,
        return Flags.enableDesktopWindowingTransitions()
                ? new SpringDragToDesktopTransitionHandler(context, transitions,
                        rootTaskDisplayAreaOrganizer, interactionJankMonitor)
                : new DefaultDragToDesktopTransitionHandler(context, transitions,
                        rootTaskDisplayAreaOrganizer, interactionJankMonitor);
    }

+240 −127
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.view.WindowManager.TRANSIT_CLOSE
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
@@ -55,8 +54,13 @@ import java.util.function.Supplier
 * Handles the transition to enter desktop from fullscreen by dragging on the handle bar. It also
 * handles the cancellation case where the task is dragged back to the status bar area in the same
 * gesture.
 *
 * It's a base sealed class that delegates flag dependant logic to its subclasses:
 * [DefaultDragToDesktopTransitionHandler] and [SpringDragToDesktopTransitionHandler]
 *
 * TODO(b/356764679): Clean up after the full flag rollout
 */
class DragToDesktopTransitionHandler(
sealed class DragToDesktopTransitionHandler(
    private val context: Context,
    private val transitions: Transitions,
    private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
@@ -64,19 +68,6 @@ class DragToDesktopTransitionHandler(
    private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
) : TransitionHandler {

    constructor(
        context: Context,
        transitions: Transitions,
        rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
        interactionJankMonitor: InteractionJankMonitor
    ) : this(
        context,
        transitions,
        rootTaskDisplayAreaOrganizer,
        interactionJankMonitor,
        Supplier { SurfaceControl.Transaction() }
    )

    private val rectEvaluator = RectEvaluator(Rect())
    private val launchHomeIntent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)

@@ -307,24 +298,18 @@ class DragToDesktopTransitionHandler(
            return false
        }

        // Layering: non-wallpaper, non-home tasks excluding the dragged task go at the bottom,
        // then Home on top of that, wallpaper on top of that and finally the dragged task on top
        // of everything.
        val appLayers = info.changes.size
        val homeLayers = info.changes.size * 2
        val wallpaperLayers = info.changes.size * 3
        val dragLayer = wallpaperLayers
        val layers = calculateStartDragToDesktopLayers(info)
        val leafTaskFilter = TransitionUtil.LeafTaskFilter()
        info.changes.withIndex().forEach { (i, change) ->
            if (TransitionUtil.isWallpaper(change)) {
                val layer = wallpaperLayers - i
                val layer = layers.wallpaperLayers - i
                startTransaction.apply {
                    setLayer(change.leash, layer)
                    show(change.leash)
                }
            } else if (isHomeChange(change)) {
                state.homeToken = change.container
                val layer = homeLayers - i
                state.homeChange = change
                val layer = layers.homeLayers - i
                startTransaction.apply {
                    setLayer(change.leash, layer)
                    show(change.leash)
@@ -338,11 +323,11 @@ class DragToDesktopTransitionHandler(
                            if (state.cancelState == CancelState.NO_CANCEL) {
                                // Normal case, split root goes to the bottom behind everything
                                // else.
                                appLayers - i
                                layers.appLayers - i
                            } else {
                                // Cancel-early case, pretend nothing happened so split root stays
                                // top.
                                dragLayer
                                layers.dragLayer
                            }
                        startTransaction.apply {
                            setLayer(change.leash, layer)
@@ -357,7 +342,7 @@ class DragToDesktopTransitionHandler(
                            state.draggedTaskChange = change
                            val bounds = change.endAbsBounds
                            startTransaction.apply {
                                setLayer(change.leash, dragLayer)
                                setLayer(change.leash, layers.dragLayer)
                                setWindowCrop(change.leash, bounds.width(), bounds.height())
                                show(change.leash)
                            }
@@ -370,7 +355,7 @@ class DragToDesktopTransitionHandler(
                            state.otherRootChanges.add(change)
                            val bounds = change.endAbsBounds
                            startTransaction.apply {
                                setLayer(change.leash, appLayers - i)
                                setLayer(change.leash, layers.appLayers - i)
                                setWindowCrop(change.leash, bounds.width(), bounds.height())
                                show(change.leash)
                            }
@@ -404,7 +389,7 @@ class DragToDesktopTransitionHandler(
                    )
                    val bounds = change.endAbsBounds
                    startTransaction.apply {
                        setLayer(change.leash, dragLayer)
                        setLayer(change.leash, layers.dragLayer)
                        setWindowCrop(change.leash, bounds.width(), bounds.height())
                        show(change.leash)
                    }
@@ -452,6 +437,15 @@ class DragToDesktopTransitionHandler(
        return true
    }

    /**
     * Calculates start drag to desktop layers for transition [info]. The leash layer is calculated
     * based on its change position in the transition, e.g. `appLayer = appLayers - i`, where i is
     * the change index.
     */
    protected abstract fun calculateStartDragToDesktopLayers(
        info: TransitionInfo
    ): DragToDesktopLayers

    override fun mergeAnimation(
        transition: IBinder,
        info: TransitionInfo,
@@ -483,40 +477,73 @@ class DragToDesktopTransitionHandler(
            state.startTransitionFinishCb
                ?: error("Start transition expected to be waiting for merge but wasn't")
        if (isEndTransition) {
            info.changes.withIndex().forEach { (i, change) ->
                // If we're exiting split, hide the remaining split task.
                if (
                    state is TransitionState.FromSplit &&
                        change.taskInfo?.taskId == state.otherSplitTask
                ) {
                    t.hide(change.leash)
                    startTransactionFinishT.hide(change.leash)
                }
                if (change.mode == TRANSIT_CLOSE) {
                    t.hide(change.leash)
                    startTransactionFinishT.hide(change.leash)
                } else if (change.taskInfo?.taskId == state.draggedTaskId) {
            setupEndDragToDesktop(
                info,
                startTransaction = t,
                finishTransaction = startTransactionFinishT
            )
            // Call finishCallback to merge animation before startTransitionFinishCb is called
            finishCallback.onTransitionFinished(null /* wct */)
            animateEndDragToDesktop(startTransaction = t, startTransitionFinishCb)
        } else if (isCancelTransition) {
            info.changes.forEach { change ->
                t.show(change.leash)
                startTransactionFinishT.show(change.leash)
            }
            t.apply()
            finishCallback.onTransitionFinished(null /* wct */)
            startTransitionFinishCb.onTransitionFinished(null /* wct */)
            clearState()
        }
    }

    protected open fun setupEndDragToDesktop(
        info: TransitionInfo,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction
    ) {
        val state = requireTransitionState()
        info.changes.forEachIndexed { i, change ->
            when {
                state is TransitionState.FromSplit &&
                    change.taskInfo?.taskId == state.otherSplitTask -> {
                    // If we're exiting split, hide the remaining split task.
                    startTransaction.hide(change.leash)
                    finishTransaction.hide(change.leash)
                }
                change.mode == TRANSIT_CLOSE -> {
                    startTransaction.hide(change.leash)
                    finishTransaction.hide(change.leash)
                }
                change.taskInfo?.taskId == state.draggedTaskId -> {
                    startTransaction.show(change.leash)
                    finishTransaction.show(change.leash)
                    state.draggedTaskChange = change
                } else if (change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM) {
                }
                change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM -> {
                    // Other freeform tasks that are being restored go behind the dragged task.
                    val draggedTaskLeash =
                        state.draggedTaskChange?.leash
                            ?: error("Expected dragged leash to be non-null")
                    t.setRelativeLayer(change.leash, draggedTaskLeash, -i)
                    startTransactionFinishT.setRelativeLayer(change.leash, draggedTaskLeash, -i)
                    startTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i)
                    finishTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i)
                }
            }
        }
    }

    private fun animateEndDragToDesktop(
        startTransaction: SurfaceControl.Transaction,
        startTransitionFinishCb: Transitions.TransitionFinishCallback
    ) {
        val state = requireTransitionState()
        val draggedTaskChange =
                state.draggedTaskChange
                    ?: throw IllegalStateException("Expected non-null change of dragged task")
            state.draggedTaskChange ?: error("Expected non-null change of dragged task")
        val draggedTaskLeash = draggedTaskChange.leash
        val startBounds = draggedTaskChange.startAbsBounds
        val endBounds = draggedTaskChange.endAbsBounds

            // Pause any animation that may be currently playing; we will use the relevant
        // Cancel any animation that may be currently playing; we will use the relevant
        // details of that animation here.
        state.dragAnimator.cancelAnimator()
        // We still apply scale to task bounds; as we animate the bounds to their
@@ -533,16 +560,15 @@ class DragToDesktopTransitionHandler(
                startPosition.y.toInt() + unscaledStartHeight
            )

            dragToDesktopStateListener?.onCommitToDesktopAnimationStart(t)
        dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
        // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
        // and finish callback. Show the veil and position the task at the first frame before
        // starting the final animation.
        onTaskResizeAnimationListener.onAnimationStart(
            state.draggedTaskId,
                t,
            startTransaction,
            unscaledStartBounds
        )
            finishCallback.onTransitionFinished(null /* wct */)
        val tx: SurfaceControl.Transaction = transactionSupplier.get()
        ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
            .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
@@ -581,16 +607,6 @@ class DragToDesktopTransitionHandler(
                )
                start()
            }
        } else if (isCancelTransition) {
            info.changes.forEach { change ->
                t.show(change.leash)
                startTransactionFinishT.show(change.leash)
            }
            t.apply()
            finishCallback.onTransitionFinished(null /* wct */)
            startTransitionFinishCb.onTransitionFinished(null /* wct */)
            clearState()
        }
    }

    override fun handleRequest(
@@ -707,7 +723,8 @@ class DragToDesktopTransitionHandler(
                wct.reorder(wc, true /* toTop */)
            }
        }
        val homeWc = state.homeToken ?: error("Home task should be non-null before cancelling")
        val homeWc =
            state.homeChange?.container ?: error("Home task should be non-null before cancelling")
        wct.restoreTransientOrder(homeWc)
    }

@@ -731,10 +748,21 @@ class DragToDesktopTransitionHandler(
        return splitScreenController.getTaskInfo(otherTaskPos)?.taskId
    }

    private fun requireTransitionState(): TransitionState {
    protected fun requireTransitionState(): TransitionState {
        return transitionState ?: error("Expected non-null transition state")
    }

    /**
     * Represents the layering (Z order) that will be given to any window based on its type during
     * the "start" transition of the drag-to-desktop transition
     */
    protected data class DragToDesktopLayers(
        val appLayers: Int,
        val homeLayers: Int,
        val wallpaperLayers: Int,
        val dragLayer: Int,
    )

    interface DragToDesktopStateListener {
        fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction)

@@ -748,7 +776,7 @@ class DragToDesktopTransitionHandler(
        abstract var startTransitionFinishCb: Transitions.TransitionFinishCallback?
        abstract var startTransitionFinishTransaction: SurfaceControl.Transaction?
        abstract var cancelTransitionToken: IBinder?
        abstract var homeToken: WindowContainerToken?
        abstract var homeChange: Change?
        abstract var draggedTaskChange: Change?
        abstract var cancelState: CancelState
        abstract var startAborted: Boolean
@@ -760,7 +788,7 @@ class DragToDesktopTransitionHandler(
            override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
            override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
            override var cancelTransitionToken: IBinder? = null,
            override var homeToken: WindowContainerToken? = null,
            override var homeChange: Change? = null,
            override var draggedTaskChange: Change? = null,
            override var cancelState: CancelState = CancelState.NO_CANCEL,
            override var startAborted: Boolean = false,
@@ -774,7 +802,7 @@ class DragToDesktopTransitionHandler(
            override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
            override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
            override var cancelTransitionToken: IBinder? = null,
            override var homeToken: WindowContainerToken? = null,
            override var homeChange: Change? = null,
            override var draggedTaskChange: Change? = null,
            override var cancelState: CancelState = CancelState.NO_CANCEL,
            override var startAborted: Boolean = false,
@@ -800,3 +828,88 @@ class DragToDesktopTransitionHandler(
        private const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L
    }
}

/** Enables flagged rollout of the [SpringDragToDesktopTransitionHandler] */
class DefaultDragToDesktopTransitionHandler
@JvmOverloads
constructor(
    context: Context,
    transitions: Transitions,
    taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
    interactionJankMonitor: InteractionJankMonitor,
    transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
        SurfaceControl.Transaction()
    },
) :
    DragToDesktopTransitionHandler(
        context,
        transitions,
        taskDisplayAreaOrganizer,
        interactionJankMonitor,
        transactionSupplier
    ) {

    /**
     * @return layers in order:
     * - appLayers - non-wallpaper, non-home tasks excluding the dragged task go at the bottom
     * - homeLayers - home task on top of apps
     * - wallpaperLayers - wallpaper on top of home
     * - dragLayer - the dragged task on top of everything, there's only 1 dragged task
     */
    override fun calculateStartDragToDesktopLayers(info: TransitionInfo): DragToDesktopLayers =
        DragToDesktopLayers(
            appLayers = info.changes.size,
            homeLayers = info.changes.size * 2,
            wallpaperLayers = info.changes.size * 3,
            dragLayer = info.changes.size * 3
        )
}

/** Desktop transition handler with spring based animation for the end drag to desktop transition */
class SpringDragToDesktopTransitionHandler
@JvmOverloads
constructor(
    context: Context,
    transitions: Transitions,
    taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
    interactionJankMonitor: InteractionJankMonitor,
    transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
        SurfaceControl.Transaction()
    },
) :
    DragToDesktopTransitionHandler(
        context,
        transitions,
        taskDisplayAreaOrganizer,
        interactionJankMonitor,
        transactionSupplier
    ) {

    /**
     * @return layers in order:
     * - appLayers - below everything z < 0, effectively hides the leash
     * - homeLayers - home task on top of apps, z in 0..<size
     * - wallpaperLayers - wallpaper on top of home, z in size..<size*2
     * - dragLayer - the dragged task on top of everything, z == size*2
     */
    override fun calculateStartDragToDesktopLayers(info: TransitionInfo): DragToDesktopLayers =
        DragToDesktopLayers(
            appLayers = -1,
            homeLayers = info.changes.size - 1,
            wallpaperLayers = info.changes.size * 2 - 1,
            dragLayer = info.changes.size * 2
        )

    override fun setupEndDragToDesktop(
        info: TransitionInfo,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction
    ) {
        super.setupEndDragToDesktop(info, startTransaction, finishTransaction)

        val state = requireTransitionState()
        val homeLeash = state.homeChange?.leash ?: error("Expects home leash to be non-null")
        // Hide home on finish to prevent flickering when wallpaper activity flag is enabled
        finishTransaction.hide(homeLeash)
    }
}
+177 −78

File changed.

Preview size limit exceeded, changes collapsed.