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

Commit e922f806 authored by mattsziklay's avatar mattsziklay
Browse files

Factor vertical split into menu position calculation.

Currently, the calculation of handle and manage windows menus is only
accurate for left-right split, not top-bottom split. This CL applies
bottom stage location when calculating desktop menu's position for
bottom stage tasks. Also migrates handle and manage windows menu
position calculation into a utility class.

Bug: 380879525
Test: Manual
Flag: EXEMPT bugfix
Change-Id: I8c4c09103c2f0bd72fdc1b26e0e6c365b3596159
parent 4a93c7cb
Loading
Loading
Loading
Loading
+19 −33
Original line number Diff line number Diff line
@@ -19,19 +19,18 @@ package com.android.wm.shell.windowdecor
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.graphics.Point
import android.graphics.Rect
import android.view.View
import android.view.WindowManager
import android.window.TaskSnapshot
import androidx.compose.ui.graphics.toArgb
import com.android.wm.shell.R
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
import com.android.wm.shell.windowdecor.common.calculateMenuPosition
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.extension.isFullscreen
import com.android.wm.shell.windowdecor.extension.isMultiWindow

/**
 * Implementation of [ManageWindowsViewContainer] meant to be used in desktop header and app
@@ -59,35 +58,19 @@ class DesktopHandleManageWindowsMenu(
    }

    private fun calculateMenuPosition(): Point {
        val position = Point()
        val nonFreeformX = (captionX + (captionWidth / 2) - (menuView.menuWidth / 2))
        when {
            callerTaskInfo.isFreeform -> {
                val taskBounds = callerTaskInfo.getConfiguration().windowConfiguration.bounds
                position.set(taskBounds.left, taskBounds.top)
            }
            callerTaskInfo.isFullscreen -> {
                position.set(nonFreeformX, 0)
            }
            callerTaskInfo.isMultiWindow -> {
                val splitPosition = splitScreenController.getSplitPosition(callerTaskInfo.taskId)
                val leftOrTopStageBounds = Rect()
                val rightOrBottomStageBounds = Rect()
                splitScreenController.getStageBounds(leftOrTopStageBounds, rightOrBottomStageBounds)
                // TODO(b/343561161): This needs to be calculated differently if the task is in
                //  top/bottom split.
                when (splitPosition) {
                    SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT -> {
                        position.set(leftOrTopStageBounds.width() + nonFreeformX, /* y= */ 0)
                    }

                    SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT -> {
                        position.set(nonFreeformX, /* y= */ 0)
                    }
                }
            }
        }
        return position
        return calculateMenuPosition(
            splitScreenController,
            callerTaskInfo,
            marginStart = 0,
            marginTop = context.resources.getDimensionPixelSize(
                R.dimen.desktop_mode_handle_menu_margin_top
            ),
            captionX,
            0,
            captionWidth,
            menuView.menuWidth,
            context.isRtl()
        )
    }

    override fun addToContainer(menuView: ManageWindowsView) {
@@ -109,4 +92,7 @@ class DesktopHandleManageWindowsMenu(
    override fun removeFromContainer() {
        menuViewContainer?.releaseView()
    }

    private fun Context.isRtl() =
        resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
}
+14 −51
Original line number Diff line number Diff line
@@ -47,12 +47,12 @@ import androidx.compose.ui.graphics.toArgb
import androidx.core.view.isGone
import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.apptoweb.isBrowserApp
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.calculateMenuPosition
import com.android.wm.shell.windowdecor.extension.isFullscreen
import com.android.wm.shell.windowdecor.extension.isMultiWindow
import com.android.wm.shell.windowdecor.extension.isPinned
@@ -240,7 +240,19 @@ class HandleMenu(
        val menuX: Int
        val menuY: Int
        val taskBounds = taskInfo.getConfiguration().windowConfiguration.bounds
        updateGlobalMenuPosition(taskBounds, captionX, captionY)
        globalMenuPosition.set(
            calculateMenuPosition(
                splitScreenController,
                taskInfo,
                marginStart = marginMenuStart,
                marginMenuTop,
                captionX,
                captionY,
                captionWidth,
                menuWidth,
                context.isRtl()
            )
        )
        if (layoutResId == R.layout.desktop_mode_app_header) {
            // Align the handle menu to the start of the header.
            menuX = if (context.isRtl()) {
@@ -265,53 +277,6 @@ class HandleMenu(
        handleMenuPosition.set(menuX.toFloat(), menuY.toFloat())
    }

    private fun updateGlobalMenuPosition(taskBounds: Rect, captionX: Int, captionY: Int) {
        val nonFreeformX = captionX + (captionWidth / 2) - (menuWidth / 2)
        when {
            taskInfo.isFreeform -> {
                if (context.isRtl()) {
                    globalMenuPosition.set(
                        /* x= */ taskBounds.right - menuWidth - marginMenuStart,
                        /* y= */ taskBounds.top + captionY + marginMenuTop
                    )
                } else {
                    globalMenuPosition.set(
                        /* x= */ taskBounds.left + marginMenuStart,
                        /* y= */ taskBounds.top + captionY + marginMenuTop
                    )
                }
            }
            taskInfo.isFullscreen -> {
                globalMenuPosition.set(
                    /* x = */ nonFreeformX,
                    /* y = */ marginMenuTop + captionY
                )
            }
            taskInfo.isMultiWindow -> {
                val splitPosition = splitScreenController.getSplitPosition(taskInfo.taskId)
                val leftOrTopStageBounds = Rect()
                val rightOrBottomStageBounds = Rect()
                splitScreenController.getStageBounds(leftOrTopStageBounds, rightOrBottomStageBounds)
                // TODO(b/343561161): This needs to be calculated differently if the task is in
                //  top/bottom split.
                when (splitPosition) {
                    SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT -> {
                        globalMenuPosition.set(
                            /* x = */ leftOrTopStageBounds.width() + nonFreeformX,
                            /* y = */ captionY + marginMenuTop
                        )
                    }
                    SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT -> {
                        globalMenuPosition.set(
                            /* x = */ nonFreeformX,
                            /* y = */ captionY + marginMenuTop
                        )
                    }
                }
            }
        }
    }

    /**
     * Update pill layout, in case task changes have caused positioning to change.
     */
@@ -374,8 +339,6 @@ class HandleMenu(
            )
            if (splitScreenController.getSplitPosition(taskInfo.taskId)
                == SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT) {
                // TODO(b/343561161): This also needs to be calculated differently if
                //  the task is in top/bottom split.
                val leftStageBounds = Rect()
                splitScreenController.getStageBounds(leftStageBounds, Rect())
                inputRelativeToMenu.x += leftStageBounds.width().toFloat()
+81 −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.common

import android.app.ActivityManager.RunningTaskInfo
import android.graphics.Point
import android.graphics.Rect
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.extension.isFullscreen

/** Utility function used for calculating position of desktop mode menus. */
fun calculateMenuPosition(
    splitScreenController: SplitScreenController,
    taskInfo: RunningTaskInfo,
    marginStart: Int,
    marginTop: Int,
    captionX: Int,
    captionY: Int,
    captionWidth: Int,
    menuWidth: Int,
    isRtl: Boolean,
): Point {
    if (taskInfo.isFreeform) {
        val taskBounds = taskInfo.configuration.windowConfiguration.bounds
        return if (isRtl) {
            Point(
                /* x= */ taskBounds.right - menuWidth - marginStart,
                /* y= */ taskBounds.top + marginTop,
            )
        } else {
            Point(/* x= */ taskBounds.left + marginStart, /* y= */ taskBounds.top + marginTop)
        }
    }
    val nonFreeformPosition = Point(captionX + (captionWidth / 2) - (menuWidth / 2), captionY)
    if (taskInfo.isFullscreen) {
        return Point(nonFreeformPosition.x, nonFreeformPosition.y + marginTop)
    }
    // Only the splitscreen case left.
    val splitPosition = splitScreenController.getSplitPosition(taskInfo.taskId)
    val leftOrTopStageBounds = Rect()
    val rightOrBottomStageBounds = Rect()
    splitScreenController.getRefStageBounds(leftOrTopStageBounds, rightOrBottomStageBounds)
    if (splitScreenController.isLeftRightSplit) {
        val rightStageModifier =
            if (splitPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
                rightOrBottomStageBounds.left
            } else {
                0
            }
        return Point(
            /* x = */ rightStageModifier + nonFreeformPosition.x,
            /* y = */ nonFreeformPosition.y + marginTop,
        )
    } else {
        val bottomSplitModifier =
            if (splitPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
                rightOrBottomStageBounds.top
            } else {
                0
            }
        return Point(
            /* x = */ nonFreeformPosition.x,
            /* y = */ nonFreeformPosition.y + bottomSplitModifier + marginTop,
        )
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -257,7 +257,7 @@ class HandleMenuTest : ShellTestCase() {
                if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
                    (SPLIT_LEFT_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2)
                } else {
                    (SPLIT_RIGHT_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2)
                    SPLIT_LEFT_BOUNDS.width() + (SPLIT_RIGHT_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2)
                }
            }
            else -> error("Invalid windowing mode")
+201 −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.common

import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.graphics.Rect
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.splitscreen.SplitScreenController
import org.junit.runner.RunWith
import kotlin.test.Test
import kotlin.test.assertEquals
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

/**
 * Tests for [DesktopMenuPositionUtility].
 *
 * Build/Install/Run: atest WMShellUnitTests:DesktopMenuPositionUtilityTest
 */
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DesktopMenuPositionUtilityTest : ShellTestCase() {

    @Mock private val mockSplitScreenController = mock<SplitScreenController>()

    @Test
    fun testFullscreenPositionCalculation() {
        val task = setupTaskInfo(WINDOWING_MODE_FULLSCREEN)
        val result =
            calculateMenuPosition(
                splitScreenController = mockSplitScreenController,
                taskInfo = task,
                MARGIN_START,
                MARGIN_TOP,
                CAPTION_X,
                CAPTION_Y,
                CAPTION_WIDTH,
                MENU_WIDTH,
                isRtl = false,
            )
        assertEquals(CAPTION_X + (CAPTION_WIDTH / 2) - (MENU_WIDTH / 2), result.x)
        assertEquals(CAPTION_Y + MARGIN_TOP, result.y)
    }

    @Test
    fun testSplitLeftPositionCalculation() {
        val task = setupTaskInfo(WINDOWING_MODE_MULTI_WINDOW)
        setupMockSplitScreenController(
            splitPosition = SPLIT_POSITION_TOP_OR_LEFT,
            isLeftRightSplit = true,
        )
        val result =
            calculateMenuPosition(
                splitScreenController = mockSplitScreenController,
                taskInfo = task,
                MARGIN_START,
                MARGIN_TOP,
                CAPTION_X,
                CAPTION_Y,
                CAPTION_WIDTH,
                MENU_WIDTH,
                isRtl = false,
            )
        assertEquals(CAPTION_X + (CAPTION_WIDTH / 2) - (MENU_WIDTH / 2), result.x)
        assertEquals(CAPTION_Y + MARGIN_TOP, result.y)
    }

    @Test
    fun testSplitRightPositionCalculation() {
        val task = setupTaskInfo(WINDOWING_MODE_MULTI_WINDOW)
        setupMockSplitScreenController(
            splitPosition = SPLIT_POSITION_BOTTOM_OR_RIGHT,
            isLeftRightSplit = true,
        )
        val result =
            calculateMenuPosition(
                splitScreenController = mockSplitScreenController,
                taskInfo = task,
                MARGIN_START,
                MARGIN_TOP,
                CAPTION_X,
                CAPTION_Y,
                CAPTION_WIDTH,
                MENU_WIDTH,
                isRtl = false,
            )
        assertEquals(
            CAPTION_X + (CAPTION_WIDTH / 2) - (MENU_WIDTH / 2) + SPLIT_LEFT_BOUNDS.width(),
            result.x,
        )
        assertEquals(CAPTION_Y + MARGIN_TOP, result.y)
    }

    @Test
    fun testSplitTopPositionCalculation() {
        val task = setupTaskInfo(WINDOWING_MODE_MULTI_WINDOW)
        setupMockSplitScreenController(
            splitPosition = SPLIT_POSITION_TOP_OR_LEFT,
            isLeftRightSplit = false,
        )
        val result =
            calculateMenuPosition(
                splitScreenController = mockSplitScreenController,
                taskInfo = task,
                MARGIN_START,
                MARGIN_TOP,
                CAPTION_X,
                CAPTION_Y,
                CAPTION_WIDTH,
                MENU_WIDTH,
                isRtl = false,
            )
        assertEquals(CAPTION_X + (CAPTION_WIDTH / 2) - (MENU_WIDTH / 2), result.x)
        assertEquals(CAPTION_Y + MARGIN_TOP, result.y)
    }

    @Test
    fun testSplitBottomPositionCalculation() {
        val task = setupTaskInfo(WINDOWING_MODE_MULTI_WINDOW)
        setupMockSplitScreenController(
            splitPosition = SPLIT_POSITION_BOTTOM_OR_RIGHT,
            isLeftRightSplit = false,
        )
        val result =
            calculateMenuPosition(
                splitScreenController = mockSplitScreenController,
                taskInfo = task,
                MARGIN_START,
                MARGIN_TOP,
                CAPTION_X,
                CAPTION_Y,
                CAPTION_WIDTH,
                MENU_WIDTH,
                isRtl = false,
            )
        assertEquals(CAPTION_X + (CAPTION_WIDTH / 2) - (MENU_WIDTH / 2), result.x)
        assertEquals(CAPTION_Y + MARGIN_TOP + SPLIT_TOP_BOUNDS.height(), result.y)
    }

    private fun setupTaskInfo(windowingMode: Int): RunningTaskInfo {
        return TestRunningTaskInfoBuilder().setWindowingMode(windowingMode).build()
    }

    private fun setupMockSplitScreenController(isLeftRightSplit: Boolean, splitPosition: Int) {
        whenever(mockSplitScreenController.getSplitPosition(anyInt())).thenReturn(splitPosition)
        whenever(mockSplitScreenController.getRefStageBounds(any(), any())).thenAnswer {
            (it.arguments.first() as Rect).set(
                if (isLeftRightSplit) {
                    SPLIT_LEFT_BOUNDS
                } else {
                    SPLIT_TOP_BOUNDS
                }
            )
            (it.arguments[1] as Rect).set(
                if (isLeftRightSplit) {
                    SPLIT_RIGHT_BOUNDS
                } else {
                    SPLIT_BOTTOM_BOUNDS
                }
            )
        }
        whenever(mockSplitScreenController.isLeftRightSplit).thenReturn(isLeftRightSplit)
    }

    companion object {
        private val SPLIT_LEFT_BOUNDS = Rect(0, 0, 1280, 1600)
        private val SPLIT_RIGHT_BOUNDS = Rect(1280, 0, 2560, 1600)
        private val SPLIT_TOP_BOUNDS = Rect(0, 0, 2560, 800)
        private val SPLIT_BOTTOM_BOUNDS = Rect(0, 800, 2560, 1600)
        private const val CAPTION_X = 800
        private const val CAPTION_Y = 50
        private const val MARGIN_START = 30
        private const val MARGIN_TOP = 50
        private const val MENU_WIDTH = 500
        private const val CAPTION_WIDTH = 200
    }
}