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

Commit be8fc8e5 authored by Matt Sziklay's avatar Matt Sziklay Committed by Android (Google) Code Review
Browse files

Merge "Factor vertical split into menu position calculation." into main

parents f1f8ac55 e922f806
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
    }
}