• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.desktopmode
18 
19 import android.app.TaskInfo
20 import android.content.res.Resources
21 import android.graphics.Point
22 import android.graphics.Rect
23 import android.view.Gravity
24 import com.android.internal.annotations.VisibleForTesting
25 import com.android.wm.shell.R
26 import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomLeft
27 import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomRight
28 import com.android.wm.shell.desktopmode.DesktopTaskPosition.Center
29 import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopLeft
30 import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopRight
31 
32 /** The position of a task window in desktop mode. */
33 sealed class DesktopTaskPosition {
34     data object Center : DesktopTaskPosition() {
35         private const val WINDOW_HEIGHT_PROPORTION = 0.375
36 
getTopLeftCoordinatesnull37         override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point {
38             val x = (frame.width() - window.width()) / 2
39             // Position with more margin at the bottom.
40             val y = (frame.height() - window.height()) * WINDOW_HEIGHT_PROPORTION + frame.top
41             return Point(x, y.toInt())
42         }
43 
nextnull44         override fun next(): DesktopTaskPosition = BottomRight
45     }
46 
47     data object BottomRight : DesktopTaskPosition() {
48         override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point =
49             Point(frame.right - window.width(), frame.bottom - window.height())
50 
51         override fun next(): DesktopTaskPosition = TopLeft
52     }
53 
54     data object TopLeft : DesktopTaskPosition() {
getTopLeftCoordinatesnull55         override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point =
56             Point(frame.left, frame.top)
57 
58         override fun next(): DesktopTaskPosition = BottomLeft
59     }
60 
61     data object BottomLeft : DesktopTaskPosition() {
62         override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point =
63             Point(frame.left, frame.bottom - window.height())
64 
65         override fun next(): DesktopTaskPosition = TopRight
66     }
67 
68     data object TopRight : DesktopTaskPosition() {
getTopLeftCoordinatesnull69         override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point =
70             Point(frame.right - window.width(), frame.top)
71 
72         override fun next(): DesktopTaskPosition = Center
73     }
74 
75     /**
76      * Returns the top left coordinates for the window to be placed in the given DesktopTaskPosition
77      * in the frame.
78      */
79     abstract fun getTopLeftCoordinates(frame: Rect, window: Rect): Point
80 
81     abstract fun next(): DesktopTaskPosition
82 }
83 
84 /**
85  * If the app has specified horizontal or vertical gravity layout, don't change the task position
86  * for cascading effect.
87  */
88 fun canChangeTaskPosition(taskInfo: TaskInfo): Boolean {
89     taskInfo.topActivityInfo?.windowLayout?.let {
90         val horizontalGravityApplied = it.gravity.and(Gravity.HORIZONTAL_GRAVITY_MASK)
91         val verticalGravityApplied = it.gravity.and(Gravity.VERTICAL_GRAVITY_MASK)
92         return horizontalGravityApplied == 0 && verticalGravityApplied == 0
93     }
94     return true
95 }
96 
97 /** Returns the current DesktopTaskPosition for a given window in the frame. */
98 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
Rectnull99 fun Rect.getDesktopTaskPosition(bounds: Rect): DesktopTaskPosition {
100     return when {
101         top == bounds.top && left == bounds.left && bottom != bounds.bottom -> TopLeft
102         top == bounds.top && right == bounds.right && bottom != bounds.bottom -> TopRight
103         bottom == bounds.bottom && left == bounds.left && top != bounds.top -> BottomLeft
104         bottom == bounds.bottom && right == bounds.right && top != bounds.top -> BottomRight
105         else -> Center
106     }
107 }
108 
cascadeWindownull109 internal fun cascadeWindow(res: Resources, frame: Rect, prev: Rect, dest: Rect) {
110     val candidateBounds = Rect(dest)
111     val lastPos = frame.getDesktopTaskPosition(prev)
112     var destCoord = Center.getTopLeftCoordinates(frame, candidateBounds)
113     candidateBounds.offsetTo(destCoord.x, destCoord.y)
114     // If the default center position is not free or if last focused window is not at the
115     // center, get the next cascading window position.
116     if (!prevBoundsMovedAboveThreshold(res, prev, candidateBounds) || Center != lastPos) {
117         val nextCascadingPos = lastPos.next()
118         destCoord = nextCascadingPos.getTopLeftCoordinates(frame, dest)
119     }
120     dest.offsetTo(destCoord.x, destCoord.y)
121 }
122 
prevBoundsMovedAboveThresholdnull123 internal fun prevBoundsMovedAboveThreshold(res: Resources, prev: Rect, newBounds: Rect): Boolean {
124     // This is the required minimum dp for a task to be touchable.
125     val moveThresholdPx =
126         res.getDimensionPixelSize(R.dimen.freeform_required_visible_empty_space_in_header)
127     val leftFar = newBounds.left - prev.left > moveThresholdPx
128     val topFar = newBounds.top - prev.top > moveThresholdPx
129     val rightFar = prev.right - newBounds.right > moveThresholdPx
130     val bottomFar = prev.bottom - newBounds.bottom > moveThresholdPx
131 
132     return leftFar || topFar || rightFar || bottomFar
133 }
134