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

Commit 0b3877a7 authored by Omar Elmekkawy's avatar Omar Elmekkawy
Browse files

[6/n] Tile resizing handle core business logic for dragging and animation

This CL introduces onDividerHandleMoved which initiates and updates the
veil for both tasks simultaneously, then at the end of drag, initiates
a WindowContainerTransaction request to resize both apps to the new bounds.

Flag: com.android.window.flags.enable_tile_resizing
Test: tests are WIP
Bug: 369341677
Change-Id: I906f87ca260eb598211e31a708ce3f505cdf8148
parent 8756450d
Loading
Loading
Loading
Loading
+107 −0
Original line number Diff line number Diff line
@@ -22,10 +22,13 @@ import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Rect
import android.os.IBinder
import android.util.Slog
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT
@@ -196,6 +199,110 @@ class DesktopTilingWindowDecoration(
        return tilingManager
    }

    fun onDividerHandleMoved(dividerBounds: Rect, t: SurfaceControl.Transaction): Boolean {
        val leftTiledTask = leftTaskResizingHelper ?: return false
        val rightTiledTask = rightTaskResizingHelper ?: return false
        val stableBounds = Rect()
        val displayLayout = displayController.getDisplayLayout(displayId)
        displayLayout?.getStableBounds(stableBounds)

        if (stableBounds.isEmpty) return false

        val leftBounds = leftTiledTask.bounds
        val rightBounds = rightTiledTask.bounds
        val newLeftBounds =
            Rect(leftBounds.left, leftBounds.top, dividerBounds.left, leftBounds.bottom)
        val newRightBounds =
            Rect(dividerBounds.right, rightBounds.top, rightBounds.right, rightBounds.bottom)

        // If one of the apps is getting smaller or bigger than size constraint, ignore finger move.
        if (
            isResizeWithinSizeConstraints(
                newLeftBounds,
                newRightBounds,
                leftBounds,
                rightBounds,
                stableBounds,
            )
        ) {
            return false
        }

        // The final new bounds for each app has to be registered to make sure a startAnimate
        // when the new bounds are different from old bounds, otherwise hide the veil without
        // waiting for an animation as no animation will run when no bounds are changed.
        leftTiledTask.newBounds.set(newLeftBounds)
        rightTiledTask.newBounds.set(newRightBounds)
        if (!isResizing) {
            leftTiledTask.showVeil(t)
            rightTiledTask.showVeil(t)
            isResizing = true
        } else {
            leftTiledTask.updateVeil(t)
            rightTiledTask.updateVeil(t)
        }

        // Applies showing/updating veil for both apps and moving the divider into its new position.
        t.apply()
        return true
    }

    fun onDividerHandleDragEnd(dividerBounds: Rect, t: SurfaceControl.Transaction) {
        val leftTiledTask = leftTaskResizingHelper ?: return
        val rightTiledTask = rightTaskResizingHelper ?: return

        if (leftTiledTask.newBounds == leftTiledTask.bounds) {
            leftTiledTask.hideVeil()
            rightTiledTask.hideVeil()
            isResizing = false
            return
        }
        leftTiledTask.bounds.set(leftTiledTask.newBounds)
        rightTiledTask.bounds.set(rightTiledTask.newBounds)
        onDividerHandleMoved(dividerBounds, t)
        isResizing = false
        val wct = WindowContainerTransaction()
        wct.setBounds(leftTiledTask.taskInfo.token, leftTiledTask.bounds)
        wct.setBounds(rightTiledTask.taskInfo.token, rightTiledTask.bounds)
        transitions.startTransition(TRANSIT_CHANGE, wct, this)
    }

    override fun startAnimation(
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: Transaction,
        finishTransaction: Transaction,
        finishCallback: Transitions.TransitionFinishCallback,
    ): Boolean {
        val leftTiledTask = leftTaskResizingHelper ?: return false
        val rightTiledTask = rightTaskResizingHelper ?: return false
        for (change in info.getChanges()) {
            val sc: SurfaceControl = change.getLeash()
            val endBounds =
                if (change.taskInfo?.taskId == leftTiledTask.taskInfo.taskId) {
                    leftTiledTask.bounds
                } else {
                    rightTiledTask.bounds
                }
            startTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
            finishTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
        }

        startTransaction.apply()
        leftTiledTask.hideVeil()
        rightTiledTask.hideVeil()
        finishCallback.onTransitionFinished(null)
        return true
    }

    // TODO(b/361505243) bring tasks to front here when the empty request info bug is fixed.
    override fun handleRequest(
        transition: IBinder,
        request: TransitionRequestInfo,
    ): WindowContainerTransaction? {
        return null
    }

    class AppResizingHelper(
        val taskInfo: RunningTaskInfo,
        val desktopModeWindowDecoration: DesktopModeWindowDecoration,
+81 −1
Original line number Diff line number Diff line
@@ -15,12 +15,16 @@
 */
package com.android.wm.shell.windowdecor.tiling

import android.app.ActivityManager
import android.content.Context
import android.content.res.Resources
import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -80,7 +84,9 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
    private val surfaceControlMock: SurfaceControl = mock()
    private val transaction: SurfaceControl.Transaction = mock()
    private val tiledTaskHelper: DesktopTilingWindowDecoration.AppResizingHelper = mock()

    private val transition: IBinder = mock()
    private val info: TransitionInfo = mock()
    private val finishCallback: Transitions.TransitionFinishCallback = mock()
    private lateinit var tilingDecoration: DesktopTilingWindowDecoration

    private val split_divider_width = 10
@@ -308,6 +314,80 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
        verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null))
    }

    @Test
    fun taskTiledTasks_NotResized_BeforeTouchEndArrival() {
        // Setup
        val task1 = createFreeformTask()
        val task2 = createFreeformTask()
        val stableBounds = STABLE_BOUNDS_MOCK
        whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
        whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
            (i.arguments.first() as Rect).set(stableBounds)
        }
        whenever(context.resources).thenReturn(resources)
        whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
        desktopWindowDecoration.mTaskInfo = task1
        task1.minWidth = 0
        task1.minHeight = 0
        initTiledTaskHelperMock(task1)
        desktopWindowDecoration.mDecorWindowContext = context
        whenever(resources.getBoolean(any())).thenReturn(true)

        // Act
        tilingDecoration.onAppTiled(
            task1,
            desktopWindowDecoration,
            DesktopTasksController.SnapPosition.RIGHT,
            BOUNDS,
        )
        tilingDecoration.onAppTiled(
            task2,
            desktopWindowDecoration,
            DesktopTasksController.SnapPosition.LEFT,
            BOUNDS,
        )

        tilingDecoration.leftTaskResizingHelper = tiledTaskHelper
        tilingDecoration.rightTaskResizingHelper = tiledTaskHelper
        tilingDecoration.onDividerHandleMoved(BOUNDS, transaction)

        // Assert
        verify(transaction, times(1)).apply()
        // Show should be called twice for each tiled app, to show the veil and the icon for each
        // of them.
        verify(tiledTaskHelper, times(2)).showVeil(any())

        // Move again
        tilingDecoration.onDividerHandleMoved(BOUNDS, transaction)
        verify(tiledTaskHelper, times(2)).updateVeil(any())
        verify(transitions, never()).startTransition(any(), any(), any())

        // End moving, no startTransition because bounds did not change.
        tiledTaskHelper.newBounds.set(BOUNDS)
        tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction)
        verify(tiledTaskHelper, times(2)).hideVeil()
        verify(transitions, never()).startTransition(any(), any(), any())

        // Move then end again with bounds changing to ensure startTransition is called.
        tilingDecoration.onDividerHandleMoved(BOUNDS, transaction)
        tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction)
        verify(transitions, times(1))
            .startTransition(eq(TRANSIT_CHANGE), any(), eq(tilingDecoration))
        // No hide veil until start animation is called.
        verify(tiledTaskHelper, times(2)).hideVeil()

        tilingDecoration.startAnimation(transition, info, transaction, transaction, finishCallback)
        // the startAnimation function should hide the veils.
        verify(tiledTaskHelper, times(4)).hideVeil()
    }

    private fun initTiledTaskHelperMock(taskInfo: ActivityManager.RunningTaskInfo) {
        whenever(tiledTaskHelper.bounds).thenReturn(BOUNDS)
        whenever(tiledTaskHelper.taskInfo).thenReturn(taskInfo)
        whenever(tiledTaskHelper.newBounds).thenReturn(Rect(BOUNDS))
        whenever(tiledTaskHelper.desktopModeWindowDecoration).thenReturn(desktopWindowDecoration)
    }

    private fun assertRectEqual(rect1: Rect, rect2: Rect) {
        assertThat(rect1.left).isEqualTo(rect2.left)
        assertThat(rect1.right).isEqualTo(rect2.right)