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

Commit 2be6afc3 authored by Qijing Yao's avatar Qijing Yao Committed by Android (Google) Code Review
Browse files

Merge "Add MultiDisplayVeiledResizeTaskPositioner" into main

parents c8afb8d8 45a406af
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -1969,7 +1969,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                Supplier<SurfaceControl.Transaction> transactionFactory,
                Handler handler) {
            final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
                    ? new VeiledResizeTaskPositioner(
                    // TODO(b/383632995): Update when the flag is launched.
                    ? (Flags.enableConnectedDisplaysWindowDrag()
                        ? new MultiDisplayVeiledResizeTaskPositioner(
                            taskOrganizer,
                            windowDecoration,
                            displayController,
@@ -1977,6 +1979,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                            transitions,
                            interactionJankMonitor,
                            handler)
                        : new VeiledResizeTaskPositioner(
                            taskOrganizer,
                            windowDecoration,
                            displayController,
                            dragEventListener,
                            transitions,
                            interactionJankMonitor,
                            handler))
                    : new FluidResizeTaskPositioner(
                            taskOrganizer,
                            transitions,
+293 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.wm.shell.windowdecor

import android.graphics.PointF
import android.graphics.Rect
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.view.Choreographer
import android.view.Surface
import android.view.SurfaceControl
import android.view.WindowManager
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.transition.Transitions
import java.util.concurrent.TimeUnit
import java.util.function.Supplier

/**
 * A task positioner that also takes into account resizing a
 * [com.android.wm.shell.windowdecor.ResizeVeil] and dragging move across multiple displays.
 * - If the drag is resizing the task, we resize the veil instead.
 * - If the drag is repositioning, we consider multi-display topology if needed, and update in the
 *   typical manner.
 */
class MultiDisplayVeiledResizeTaskPositioner(
    private val taskOrganizer: ShellTaskOrganizer,
    private val desktopWindowDecoration: DesktopModeWindowDecoration,
    private val displayController: DisplayController,
    dragEventListener: DragPositioningCallbackUtility.DragEventListener,
    private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
    private val transitions: Transitions,
    private val interactionJankMonitor: InteractionJankMonitor,
    @ShellMainThread private val handler: Handler,
) : TaskPositioner, Transitions.TransitionHandler {
    private val dragEventListeners =
        mutableListOf<DragPositioningCallbackUtility.DragEventListener>()
    private val stableBounds = Rect()
    private val taskBoundsAtDragStart = Rect()
    private val repositionStartPoint = PointF()
    private val repositionTaskBounds = Rect()
    private val isResizing: Boolean
        get() =
            (ctrlType and DragPositioningCallback.CTRL_TYPE_TOP) != 0 ||
                (ctrlType and DragPositioningCallback.CTRL_TYPE_BOTTOM) != 0 ||
                (ctrlType and DragPositioningCallback.CTRL_TYPE_LEFT) != 0 ||
                (ctrlType and DragPositioningCallback.CTRL_TYPE_RIGHT) != 0

    @DragPositioningCallback.CtrlType private var ctrlType = 0
    private var isResizingOrAnimatingResize = false
    @Surface.Rotation private var rotation = 0

    constructor(
        taskOrganizer: ShellTaskOrganizer,
        windowDecoration: DesktopModeWindowDecoration,
        displayController: DisplayController,
        dragEventListener: DragPositioningCallbackUtility.DragEventListener,
        transitions: Transitions,
        interactionJankMonitor: InteractionJankMonitor,
        @ShellMainThread handler: Handler,
    ) : this(
        taskOrganizer,
        windowDecoration,
        displayController,
        dragEventListener,
        Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() },
        transitions,
        interactionJankMonitor,
        handler,
    )

    init {
        dragEventListeners.add(dragEventListener)
    }

    override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect {
        this.ctrlType = ctrlType
        taskBoundsAtDragStart.set(
            desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.bounds
        )
        repositionStartPoint[x] = y
        if (isResizing) {
            // Capture CUJ for re-sizing window in DW mode.
            interactionJankMonitor.begin(
                createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
            )
            if (!desktopWindowDecoration.mHasGlobalFocus) {
                val wct = WindowContainerTransaction()
                wct.reorder(
                    desktopWindowDecoration.mTaskInfo.token,
                    /* onTop= */ true,
                    /* includingParents= */ true,
                )
                taskOrganizer.applyTransaction(wct)
            }
        }
        for (dragEventListener in dragEventListeners) {
            dragEventListener.onDragStart(desktopWindowDecoration.mTaskInfo.taskId)
        }
        repositionTaskBounds.set(taskBoundsAtDragStart)
        val rotation =
            desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.displayRotation
        if (stableBounds.isEmpty || this.rotation != rotation) {
            this.rotation = rotation
            displayController
                .getDisplayLayout(desktopWindowDecoration.mDisplay.displayId)!!
                .getStableBounds(stableBounds)
        }
        return Rect(repositionTaskBounds)
    }

    override fun onDragPositioningMove(displayId: Int, x: Float, y: Float): Rect {
        check(Looper.myLooper() == handler.looper) {
            "This method must run on the shell main thread."
        }
        val delta = DragPositioningCallbackUtility.calculateDelta(x, y, repositionStartPoint)
        if (
            isResizing &&
                DragPositioningCallbackUtility.changeBounds(
                    ctrlType,
                    repositionTaskBounds,
                    taskBoundsAtDragStart,
                    stableBounds,
                    delta,
                    displayController,
                    desktopWindowDecoration,
                )
        ) {
            if (!isResizingOrAnimatingResize) {
                for (dragEventListener in dragEventListeners) {
                    dragEventListener.onDragMove(desktopWindowDecoration.mTaskInfo.taskId)
                }
                desktopWindowDecoration.showResizeVeil(repositionTaskBounds)
                isResizingOrAnimatingResize = true
            } else {
                desktopWindowDecoration.updateResizeVeil(repositionTaskBounds)
            }
        } else if (ctrlType == DragPositioningCallback.CTRL_TYPE_UNDEFINED) {
            // Begin window drag CUJ instrumentation only when drag position moves.
            interactionJankMonitor.begin(
                createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
            )
            val t = transactionSupplier.get()
            DragPositioningCallbackUtility.setPositionOnDrag(
                desktopWindowDecoration,
                repositionTaskBounds,
                taskBoundsAtDragStart,
                repositionStartPoint,
                t,
                x,
                y,
            )
            t.setFrameTimeline(Choreographer.getInstance().vsyncId)
            t.apply()
        }
        return Rect(repositionTaskBounds)
    }

    override fun onDragPositioningEnd(displayId: Int, x: Float, y: Float): Rect {
        val delta = DragPositioningCallbackUtility.calculateDelta(x, y, repositionStartPoint)
        if (isResizing) {
            if (taskBoundsAtDragStart != repositionTaskBounds) {
                DragPositioningCallbackUtility.changeBounds(
                    ctrlType,
                    repositionTaskBounds,
                    taskBoundsAtDragStart,
                    stableBounds,
                    delta,
                    displayController,
                    desktopWindowDecoration,
                )
                desktopWindowDecoration.updateResizeVeil(repositionTaskBounds)
                val wct = WindowContainerTransaction()
                wct.setBounds(desktopWindowDecoration.mTaskInfo.token, repositionTaskBounds)
                transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, this)
            } else {
                // If bounds haven't changed, perform necessary veil reset here as startAnimation
                // won't be called.
                resetVeilIfVisible()
            }
            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
        } else {
            DragPositioningCallbackUtility.updateTaskBounds(
                repositionTaskBounds,
                taskBoundsAtDragStart,
                repositionStartPoint,
                x,
                y,
            )
            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
        }

        ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED
        taskBoundsAtDragStart.setEmpty()
        repositionStartPoint[0f] = 0f
        return Rect(repositionTaskBounds)
    }

    private fun resetVeilIfVisible() {
        if (isResizingOrAnimatingResize) {
            desktopWindowDecoration.hideResizeVeil()
            isResizingOrAnimatingResize = false
        }
    }

    private fun createLongTimeoutJankConfigBuilder(@Cuj.CujType cujType: Int) =
        InteractionJankMonitor.Configuration.Builder.withSurface(
                cujType,
                desktopWindowDecoration.mContext,
                desktopWindowDecoration.mTaskSurface,
                handler,
            )
            .setTimeout(LONG_CUJ_TIMEOUT_MS)

    override fun startAnimation(
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: SurfaceControl.Transaction,
        finishTransaction: SurfaceControl.Transaction,
        finishCallback: Transitions.TransitionFinishCallback,
    ): Boolean {
        for (change in info.changes) {
            val sc = change.leash
            val endBounds = change.endAbsBounds
            val endPosition = change.endRelOffset
            startTransaction
                .setWindowCrop(sc, endBounds.width(), endBounds.height())
                .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
            finishTransaction
                .setWindowCrop(sc, endBounds.width(), endBounds.height())
                .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
        }

        startTransaction.apply()
        resetVeilIfVisible()
        ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED
        finishCallback.onTransitionFinished(null /* wct */)
        isResizingOrAnimatingResize = false
        interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
        return true
    }

    /**
     * We should never reach this as this handler's transitions are only started from shell
     * explicitly.
     */
    override fun handleRequest(
        transition: IBinder,
        request: TransitionRequestInfo,
    ): WindowContainerTransaction? {
        return null
    }

    override fun isResizingOrAnimating() = isResizingOrAnimatingResize

    override fun addDragEventListener(
        dragEventListener: DragPositioningCallbackUtility.DragEventListener
    ) {
        dragEventListeners.add(dragEventListener)
    }

    override fun removeDragEventListener(
        dragEventListener: DragPositioningCallbackUtility.DragEventListener
    ) {
        dragEventListeners.remove(dragEventListener)
    }

    companion object {
        // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
        // timing out in the middle of a resize or drag action.
        private val LONG_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(/* duration= */ 10L)
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -53,7 +53,12 @@ import java.util.function.Supplier;
 * {@link com.android.wm.shell.windowdecor.ResizeVeil}.
 * If the drag is resizing the task, we resize the veil instead.
 * If the drag is repositioning, we update in the typical manner.
 * <p>
 * @deprecated This class will be replaced by
 * {@link com.android.wm.shell.windowdecor.MultiDisplayVeiledResizeTaskPositioner}.
 * TODO(b/383632995): Remove this class after MultiDisplayVeiledResizeTaskPositioner is launched.
 */
@Deprecated
public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.TransitionHandler {
    // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
    // timing out in the middle of a resize or drag action.
+611 −0

File added.

Preview size limit exceeded, changes collapsed.