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

Commit 9160446e authored by Qijing Yao's avatar Qijing Yao
Browse files

Defer drag indicator removal until window drop transition ends

Synchronize the removal of multi-display drag indicators with the
completion of the window drop animation for a smoother user experience.

Previously, when a window drag operation ended, the drag-move indicators
were hidden immediately. If the drop triggered a window drag transition,
the indicators would disappear before the window animation completed,
creating a visual jank.

This change adds a call to remove drag indicators inside the transition
handler to resolve the issue, this ensures the indicators disappears
smoothly as part of the window's final placement animation.

We also switch to use main thread for window drag indicator surface
creation and removal. ShellDesktopThread was used in the past, because
we had heavy operations like creating SurfaceControlViewHost. But since
we have switched to use mirrored surface, doing all these operations in
ShellDesktopThread is not needed anymore.

Flag: com.android.window.flags.enable_window_drop_smooth_transition
Bug: 398992368
Test: manual and atest
Change-Id: I6f3f54de85bd564158b1cfcaf91a1955964cf575
parent 0d147139
Loading
Loading
Loading
Loading
+64 −77
Original line number Diff line number Diff line
@@ -18,8 +18,8 @@ package com.android.wm.shell.common
import android.app.ActivityManager.RunningTaskInfo
import android.graphics.RectF
import android.view.SurfaceControl
import android.window.DesktopExperienceFlags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.shared.annotations.ShellDesktopThread
import com.android.wm.shell.shared.desktopmode.DesktopState

/**
@@ -30,10 +30,8 @@ class MultiDisplayDragMoveIndicatorController(
    private val displayController: DisplayController,
    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
    private val indicatorSurfaceFactory: MultiDisplayDragMoveIndicatorSurface.Factory,
    @ShellDesktopThread private val desktopExecutor: ShellExecutor,
    private val desktopState: DesktopState,
) {
    @ShellDesktopThread
    private val dragIndicators =
        mutableMapOf<Int, MutableMap<Int, MultiDisplayDragMoveIndicatorSurface>>()

@@ -43,9 +41,6 @@ class MultiDisplayDragMoveIndicatorController(
     * [taskInfo] based on [boundsDp] on the destination displays ([displayIds]) as the dragged
     * window moves. [transactionSupplier] provides a [SurfaceControl.Transaction] for applying
     * changes to the indicator surfaces.
     *
     * It is executed on the [desktopExecutor] to prevent blocking the main thread and avoid jank,
     * as creating and manipulating surfaces can be expensive.
     */
    fun onDragMove(
        boundsDp: RectF,
@@ -56,9 +51,8 @@ class MultiDisplayDragMoveIndicatorController(
        displayIds: Set<Int>,
        transactionSupplier: () -> SurfaceControl.Transaction,
    ) {
        desktopExecutor.execute {
        val startDisplayDpi =
                displayController.getDisplayLayout(startDisplayId)?.densityDpi() ?: return@execute
            displayController.getDisplayLayout(startDisplayId)?.densityDpi() ?: return
        val transaction = transactionSupplier()
        for (displayId in displayIds) {
            if (
@@ -98,8 +92,7 @@ class MultiDisplayDragMoveIndicatorController(
                )

            // Get or create the inner map for the current task.
                val dragIndicatorsForTask =
                    dragIndicators.getOrPut(taskInfo.taskId) { mutableMapOf() }
            val dragIndicatorsForTask = dragIndicators.getOrPut(taskInfo.taskId) { mutableMapOf() }
            dragIndicatorsForTask[displayId]?.also { existingIndicator ->
                existingIndicator.relayout(boundsPx, transaction, visibility)
            }
@@ -119,25 +112,19 @@ class MultiDisplayDragMoveIndicatorController(
        }
        transaction.apply()
    }
    }

    /**
     * Called when the drag ends. Disposes of the drag move indicator surfaces associated with the
     * given [taskId]. [transactionSupplier] provides a [SurfaceControl.Transaction] for applying
     * changes to the indicator surfaces.
     *
     * It is executed on the [desktopExecutor] to ensure that any pending `onDragMove` operations
     * have completed before disposing of the surfaces.
     * given [taskId] and applies the surface changes with the provided [transaction].
     */
    fun onDragEnd(taskId: Int, transactionSupplier: () -> SurfaceControl.Transaction) {
        desktopExecutor.execute {
    fun onDragEnd(taskId: Int, transaction: SurfaceControl.Transaction) {
        dragIndicators
            .remove(taskId)
            ?.values
            ?.takeIf { it.isNotEmpty() }
            ?.let { indicators ->
                    val transaction = transactionSupplier()
                indicators.forEach { indicator -> indicator.dispose(transaction) }
                if (!DesktopExperienceFlags.ENABLE_WINDOW_DROP_SMOOTH_TRANSITION.isTrue) {
                    transaction.apply()
                }
            }
+8 −7
Original line number Diff line number Diff line
@@ -126,7 +126,6 @@ import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.DisplayDisconnectTransitionHandler;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DragToDisplayTransitionHandler;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.OverviewToDesktopTransitionObserver;
@@ -137,6 +136,7 @@ import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.VisualIndicatorUpdateScheduler;
import com.android.wm.shell.desktopmode.WindowDecorCaptionRepository;
import com.android.wm.shell.desktopmode.WindowDragTransitionHandler;
import com.android.wm.shell.desktopmode.compatui.SystemModalsTransitionHandler;
import com.android.wm.shell.desktopmode.data.DesktopRepositoryInitializer;
import com.android.wm.shell.desktopmode.data.DesktopRepositoryInitializerImpl;
@@ -921,7 +921,7 @@ public abstract class WMShellModule {
            Optional<DesksTransitionObserver> desksTransitionObserver,
            UserProfileContexts userProfileContexts,
            DesktopModeCompatPolicy desktopModeCompatPolicy,
            DragToDisplayTransitionHandler dragToDisplayTransitionHandler,
            WindowDragTransitionHandler windowDragTransitionHandler,
            DesktopModeMoveToDisplayTransitionHandler moveToDisplayTransitionHandler,
            HomeIntentProvider homeIntentProvider,
            DesktopState desktopState,
@@ -969,7 +969,7 @@ public abstract class WMShellModule {
                desksTransitionObserver.get(),
                userProfileContexts,
                desktopModeCompatPolicy,
                dragToDisplayTransitionHandler,
                windowDragTransitionHandler,
                moveToDisplayTransitionHandler,
                homeIntentProvider,
                desktopState,
@@ -1160,8 +1160,10 @@ public abstract class WMShellModule {

    @WMSingleton
    @Provides
    static DragToDisplayTransitionHandler provideDragToDisplayTransitionHandler() {
        return new DragToDisplayTransitionHandler();
    static WindowDragTransitionHandler provideWindowDragTransitionHandler(
            MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController
    ) {
        return new WindowDragTransitionHandler(multiDisplayDragMoveIndicatorController);
    }

    @WMSingleton
@@ -1288,12 +1290,11 @@ public abstract class WMShellModule {
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            MultiDisplayDragMoveIndicatorSurface.Factory
                multiDisplayDragMoveIndicatorSurfaceFactory,
            @ShellDesktopThread ShellExecutor desktopExecutor,
            DesktopState desktopState
    ) {
        return new MultiDisplayDragMoveIndicatorController(
                displayController, rootTaskDisplayAreaOrganizer,
                multiDisplayDragMoveIndicatorSurfaceFactory, desktopExecutor, desktopState);
                multiDisplayDragMoveIndicatorSurfaceFactory, desktopState);
    }

    @WMSingleton
+13 −9
Original line number Diff line number Diff line
@@ -242,7 +242,7 @@ class DesktopTasksController(
    private val desksTransitionObserver: DesksTransitionObserver,
    private val userProfileContexts: UserProfileContexts,
    private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
    private val dragToDisplayTransitionHandler: DragToDisplayTransitionHandler,
    private val windowDragTransitionHandler: WindowDragTransitionHandler,
    private val moveToDisplayTransitionHandler: DesktopModeMoveToDisplayTransitionHandler,
    private val homeIntentProvider: HomeIntentProvider,
    private val desktopState: DesktopState,
@@ -4996,6 +4996,7 @@ class DesktopTasksController(
     *   task bounds or just task leash)
     * @param validDragArea the bounds of where the task can be dragged within the display.
     * @param dragStartBounds the bounds of the task before starting dragging.
     * @return true if caller needs to clear the window drag indicators by itself.
     */
    fun onDragPositioningEnd(
        taskInfo: RunningTaskInfo,
@@ -5006,17 +5007,19 @@ class DesktopTasksController(
        validDragArea: Rect,
        dragStartBounds: Rect,
        motionEvent: MotionEvent,
    ) {
    ): Boolean {
        if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
            return
            return true
        }

        val indicator = getVisualIndicator() ?: return
        val indicator = getVisualIndicator() ?: return true
        val indicatorType =
            indicator.updateIndicatorType(
                displayId,
                PointF(inputCoordinate.x, currentDragBounds.top.toFloat()),
            )

        var needDragIndicatorCleanup = true
        when (indicatorType) {
            IndicatorType.TO_FULLSCREEN_INDICATOR -> {
                val shouldMaximizeWhenDragToTopEdge =
@@ -5092,10 +5095,10 @@ class DesktopTasksController(
                        startBounds = currentDragBounds,
                        endBounds = dragStartBounds,
                    )
                    return
                    return true
                }

                val newDisplayId = motionEvent.getDisplayId()
                val newDisplayId = motionEvent.displayId
                val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(newDisplayId)
                val isCrossDisplayDrag =
                    DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG.isTrue() &&
@@ -5121,7 +5124,7 @@ class DesktopTasksController(
                        taskInfo,
                        newDisplayId,
                        constrainedBounds,
                        dragToDisplayTransitionHandler,
                        windowDragTransitionHandler,
                        enterReason = EnterReason.APP_HANDLE_DRAG,
                    )
                } else {
@@ -5129,10 +5132,10 @@ class DesktopTasksController(
                    // leash
                    val wct = WindowContainerTransaction()
                    wct.setBounds(taskInfo.token, destinationBounds)
                    transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
                    transitions.startTransition(TRANSIT_CHANGE, wct, windowDragTransitionHandler)
                }

                releaseVisualIndicator()
                needDragIndicatorCleanup = false
            }
            IndicatorType.TO_DESKTOP_INDICATOR -> {
                throw IllegalArgumentException(
@@ -5145,6 +5148,7 @@ class DesktopTasksController(
        taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
            doesAnyTaskRequireTaskbarRounding(taskInfo.displayId)
        )
        return needDragIndicatorCleanup
    }

    /**
+13 −1
Original line number Diff line number Diff line
@@ -17,13 +17,17 @@ package com.android.wm.shell.desktopmode

import android.os.IBinder
import android.view.SurfaceControl
import android.window.DesktopExperienceFlags
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController
import com.android.wm.shell.transition.Transitions

/** Handles the transition to drag a window to another display by dragging the caption. */
class DragToDisplayTransitionHandler : Transitions.TransitionHandler {
class WindowDragTransitionHandler(
    private val multiDisplayDragMoveIndicatorController: MultiDisplayDragMoveIndicatorController
) : Transitions.TransitionHandler {
    override fun handleRequest(
        transition: IBinder,
        request: TransitionRequestInfo,
@@ -48,6 +52,14 @@ class DragToDisplayTransitionHandler : Transitions.TransitionHandler {
            finishTransaction
                .setWindowCrop(sc, endBounds.width(), endBounds.height())
                .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
            if (DesktopExperienceFlags.ENABLE_WINDOW_DROP_SMOOTH_TRANSITION.isTrue) {
                change.taskInfo?.let { taskInfo ->
                    multiDisplayDragMoveIndicatorController.onDragEnd(
                        taskInfo.taskId,
                        finishTransaction,
                    )
                }
            }
        }

        startTransaction.apply()
+14 −6
Original line number Diff line number Diff line
@@ -1564,12 +1564,20 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                    // Tasks bounds haven't actually been updated (only its leash), so pass to
                    // DesktopTasksController to allow secondary transformations (i.e. snap resizing
                    // or transforming to fullscreen) before setting new task bounds.
                    final boolean needDragIndicatorCleanup =
                            mDesktopTasksController.onDragPositioningEnd(
                            taskInfo, decoration.getTaskSurface(),
                            e.getDisplayId(),
                            new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
                                    taskInfo, decoration.getTaskSurface(), e.getDisplayId(),
                                    new PointF(e.getRawX(dragPointerIdx),
                                            e.getRawY(dragPointerIdx)),
                                    newTaskBounds, decoration.calculateValidDragArea(),
                                    new Rect(mOnDragStartInitialBounds), e);
                    if (DesktopExperienceFlags.ENABLE_WINDOW_DROP_SMOOTH_TRANSITION.isTrue()) {
                        if (needDragIndicatorCleanup) {
                            mMultiDisplayDragMoveIndicatorController.onDragEnd(taskInfo.taskId,
                                    mTransactionFactory.get());
                        }
                    }

                    if (touchingButton) {
                        // We need the input event to not be consumed here to end the ripple
                        // effect on the touched button. We will reset drag state in the ensuing
Loading