Limit resizing to within the stable bounds
Resizing into the navbar area caused the task's bottom resize handles to be unaccesible for new drag-resize interactions because the touch/drag would be stolen by the system gesture detector to swipe up. This change disallows the task to be resized into the gesture area in the first place, so that there's no overlap between the task's edges and the gesture region. Note that it is still possible to run into this overlap if the task is moved, not resized, into the gesture area. Whether the task should be forbidden from moving there (and offscreen), or if the gesture should add the task edges as 'exclusion' regions is TBD. Bug: 269659818 Test: atest WMShellUnitTests Change-Id: I2dfe037062dad46db93e0e54c94e0cfdafa76bf6
This commit is contained in:
@@ -40,6 +40,7 @@ class TaskPositioner implements DragPositioningCallback {
|
||||
private final DisplayController mDisplayController;
|
||||
private final WindowDecoration mWindowDecoration;
|
||||
|
||||
private final Rect mTempBounds = new Rect();
|
||||
private final Rect mTaskBoundsAtDragStart = new Rect();
|
||||
private final PointF mRepositionStartPoint = new PointF();
|
||||
private final Rect mRepositionTaskBounds = new Rect();
|
||||
@@ -117,17 +118,32 @@ class TaskPositioner implements DragPositioningCallback {
|
||||
final float deltaX = x - mRepositionStartPoint.x;
|
||||
final float deltaY = y - mRepositionStartPoint.y;
|
||||
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
|
||||
|
||||
final Rect stableBounds = mTempBounds;
|
||||
// Make sure the new resizing destination in any direction falls within the stable bounds.
|
||||
// If not, set the bounds back to the old location that was valid to avoid conflicts with
|
||||
// some regions such as the gesture area.
|
||||
mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
|
||||
.getStableBounds(stableBounds);
|
||||
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
|
||||
mRepositionTaskBounds.left += deltaX;
|
||||
final int candidateLeft = mRepositionTaskBounds.left + (int) deltaX;
|
||||
mRepositionTaskBounds.left = (candidateLeft > stableBounds.left)
|
||||
? candidateLeft : oldLeft;
|
||||
}
|
||||
if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
|
||||
mRepositionTaskBounds.right += deltaX;
|
||||
final int candidateRight = mRepositionTaskBounds.right + (int) deltaX;
|
||||
mRepositionTaskBounds.right = (candidateRight < stableBounds.right)
|
||||
? candidateRight : oldRight;
|
||||
}
|
||||
if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
|
||||
mRepositionTaskBounds.top += deltaY;
|
||||
final int candidateTop = mRepositionTaskBounds.top + (int) deltaY;
|
||||
mRepositionTaskBounds.top = (candidateTop > stableBounds.top)
|
||||
? candidateTop : oldTop;
|
||||
}
|
||||
if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
|
||||
mRepositionTaskBounds.bottom += deltaY;
|
||||
final int candidateBottom = mRepositionTaskBounds.bottom + (int) deltaY;
|
||||
mRepositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom)
|
||||
? candidateBottom : oldBottom;
|
||||
}
|
||||
if (mCtrlType == CTRL_TYPE_UNDEFINED) {
|
||||
mRepositionTaskBounds.offset((int) deltaX, (int) deltaY);
|
||||
|
||||
@@ -5,13 +5,16 @@ import android.app.WindowConfiguration
|
||||
import android.graphics.Rect
|
||||
import android.os.IBinder
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.view.Display
|
||||
import android.window.WindowContainerToken
|
||||
import android.window.WindowContainerTransaction
|
||||
import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.wm.shell.common.DisplayController
|
||||
import com.android.wm.shell.common.DisplayLayout
|
||||
import com.android.wm.shell.ShellTaskOrganizer
|
||||
import com.android.wm.shell.ShellTestCase
|
||||
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_BOTTOM
|
||||
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
|
||||
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_TOP
|
||||
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
|
||||
@@ -19,10 +22,11 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.any
|
||||
import org.mockito.Mockito.argThat
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
/**
|
||||
@@ -51,6 +55,8 @@ class TaskPositionerTest : ShellTestCase() {
|
||||
private lateinit var mockDisplayController: DisplayController
|
||||
@Mock
|
||||
private lateinit var mockDisplayLayout: DisplayLayout
|
||||
@Mock
|
||||
private lateinit var mockDisplay: Display
|
||||
|
||||
private lateinit var taskPositioner: TaskPositioner
|
||||
|
||||
@@ -68,6 +74,9 @@ class TaskPositionerTest : ShellTestCase() {
|
||||
`when`(taskToken.asBinder()).thenReturn(taskBinder)
|
||||
`when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
|
||||
`when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
|
||||
`when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
|
||||
(i.arguments.first() as Rect).set(STABLE_BOUNDS)
|
||||
}
|
||||
|
||||
mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
|
||||
taskId = TASK_ID
|
||||
@@ -78,6 +87,8 @@ class TaskPositionerTest : ShellTestCase() {
|
||||
displayId = DISPLAY_ID
|
||||
configuration.windowConfiguration.bounds = STARTING_BOUNDS
|
||||
}
|
||||
mockWindowDecoration.mDisplay = mockDisplay
|
||||
`when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -451,6 +462,72 @@ class TaskPositionerTest : ShellTestCase() {
|
||||
})
|
||||
}
|
||||
|
||||
fun testDragResize_toDisallowedBounds_freezesAtLimit() {
|
||||
taskPositioner.onDragPositioningStart(
|
||||
CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, // Resize right-bottom corner
|
||||
STARTING_BOUNDS.right.toFloat(),
|
||||
STARTING_BOUNDS.bottom.toFloat()
|
||||
)
|
||||
|
||||
// Resize the task by 10px to the right and bottom, a valid destination
|
||||
val newBounds = Rect(
|
||||
STARTING_BOUNDS.left,
|
||||
STARTING_BOUNDS.top,
|
||||
STARTING_BOUNDS.right + 10,
|
||||
STARTING_BOUNDS.bottom + 10)
|
||||
taskPositioner.onDragPositioningMove(
|
||||
newBounds.right.toFloat(),
|
||||
newBounds.bottom.toFloat()
|
||||
)
|
||||
|
||||
// Resize the task by another 10px to the right (allowed) and to just in the disallowed
|
||||
// area of the Y coordinate.
|
||||
val newBounds2 = Rect(
|
||||
newBounds.left,
|
||||
newBounds.top,
|
||||
newBounds.right + 10,
|
||||
DISALLOWED_RESIZE_AREA.top
|
||||
)
|
||||
taskPositioner.onDragPositioningMove(
|
||||
newBounds2.right.toFloat(),
|
||||
newBounds2.bottom.toFloat()
|
||||
)
|
||||
|
||||
taskPositioner.onDragPositioningEnd(newBounds2.right.toFloat(), newBounds2.bottom.toFloat())
|
||||
|
||||
// The first resize falls in the allowed area, verify there's a change for it.
|
||||
verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
|
||||
return@argThat wct.changes.any { (token, change) ->
|
||||
token == taskBinder && change.ofBounds(newBounds)
|
||||
}
|
||||
})
|
||||
// The second resize falls in the disallowed area, verify there's no change for it.
|
||||
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
|
||||
return@argThat wct.changes.any { (token, change) ->
|
||||
token == taskBinder && change.ofBounds(newBounds2)
|
||||
}
|
||||
})
|
||||
// Instead, there should be a change for its allowed portion (the X movement) with the Y
|
||||
// staying frozen in the last valid resize position.
|
||||
verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
|
||||
return@argThat wct.changes.any { (token, change) ->
|
||||
token == taskBinder && change.ofBounds(
|
||||
Rect(
|
||||
newBounds2.left,
|
||||
newBounds2.top,
|
||||
newBounds2.right,
|
||||
newBounds.bottom // Stayed at the first resize destination.
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
|
||||
return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
|
||||
bounds == configuration.windowConfiguration.bounds
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TASK_ID = 5
|
||||
private const val MIN_WIDTH = 10
|
||||
@@ -458,6 +535,19 @@ class TaskPositionerTest : ShellTestCase() {
|
||||
private const val DENSITY_DPI = 20
|
||||
private const val DEFAULT_MIN = 40
|
||||
private const val DISPLAY_ID = 1
|
||||
private const val NAVBAR_HEIGHT = 50
|
||||
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
|
||||
private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
|
||||
private val DISALLOWED_RESIZE_AREA = Rect(
|
||||
DISPLAY_BOUNDS.left,
|
||||
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
|
||||
DISPLAY_BOUNDS.right,
|
||||
DISPLAY_BOUNDS.bottom)
|
||||
private val STABLE_BOUNDS = Rect(
|
||||
DISPLAY_BOUNDS.left,
|
||||
DISPLAY_BOUNDS.top,
|
||||
DISPLAY_BOUNDS.right,
|
||||
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user