Merge "Improve handling of TV PiPs bigger than movement bounds" into tm-dev am: 943ffdb6c1

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17717063

Change-Id: I9bf2336d9ac46ced3b1795c9e81e49857511c974
Ignore-AOSP-First: this is an automerge
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Robert Horvath
2022-04-26 08:31:37 +00:00
committed by Automerger Merge Worker
2 changed files with 94 additions and 53 deletions

View File

@@ -150,7 +150,7 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
return Placement( return Placement(
pipBounds, pipBounds,
anchorBounds, anchorBounds,
getStashType(pipBounds, movementBounds), getStashType(pipBounds, unstashedDestBounds),
unstashedDestBounds, unstashedDestBounds,
result.unstashTime result.unstashTime
) )
@@ -185,7 +185,10 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
restrictedAreas: Set<Rect>, restrictedAreas: Set<Rect>,
unrestrictedAreas: Set<Rect> unrestrictedAreas: Set<Rect>
): Placement { ): Placement {
if (restrictedAreas.isEmpty() && unrestrictedAreas.isEmpty()) { // If PiP is not covered by any keep clear areas, we can leave it at the anchor bounds
val keepClearAreas = restrictedAreas + unrestrictedAreas
if (keepClearAreas.none { it.intersects(pipAnchorBounds) }) {
lastAreasOverlappingUnstashPosition = emptySet()
return Placement(pipAnchorBounds, pipAnchorBounds) return Placement(pipAnchorBounds, pipAnchorBounds)
} }
@@ -204,9 +207,8 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
?: findFreeMovePosition(pipAnchorBounds, emptySet(), unrestrictedAreas) ?: findFreeMovePosition(pipAnchorBounds, emptySet(), unrestrictedAreas)
?: pipAnchorBounds ?: pipAnchorBounds
val keepClearAreas = restrictedAreas + unrestrictedAreas
val areasOverlappingUnstashPosition = val areasOverlappingUnstashPosition =
keepClearAreas.filter { Rect.intersects(it, unstashBounds) }.toSet() keepClearAreas.filterTo(mutableSetOf()) { it.intersects(unstashBounds) }
val areasOverlappingUnstashPositionChanged = val areasOverlappingUnstashPositionChanged =
!lastAreasOverlappingUnstashPosition.containsAll(areasOverlappingUnstashPosition) !lastAreasOverlappingUnstashPosition.containsAll(areasOverlappingUnstashPosition)
lastAreasOverlappingUnstashPosition = areasOverlappingUnstashPosition lastAreasOverlappingUnstashPosition = areasOverlappingUnstashPosition
@@ -228,19 +230,22 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
return Placement( return Placement(
stashedBounds, stashedBounds,
pipAnchorBounds, pipAnchorBounds,
getStashType(stashedBounds, transformedMovementBounds), getStashType(stashedBounds, unstashBounds),
unstashBounds, unstashBounds,
unstashTime unstashTime
) )
} }
@PipBoundsState.StashType @PipBoundsState.StashType
private fun getStashType(stashedBounds: Rect, movementBounds: Rect): Int { private fun getStashType(stashedBounds: Rect, unstashedDestBounds: Rect?): Int {
if (unstashedDestBounds == null) {
return STASH_TYPE_NONE
}
return when { return when {
stashedBounds.left < movementBounds.left -> STASH_TYPE_LEFT stashedBounds.left < unstashedDestBounds.left -> STASH_TYPE_LEFT
stashedBounds.right > movementBounds.right -> STASH_TYPE_RIGHT stashedBounds.right > unstashedDestBounds.right -> STASH_TYPE_RIGHT
stashedBounds.top < movementBounds.top -> STASH_TYPE_TOP stashedBounds.top < unstashedDestBounds.top -> STASH_TYPE_TOP
stashedBounds.bottom > movementBounds.bottom -> STASH_TYPE_BOTTOM stashedBounds.bottom > unstashedDestBounds.bottom -> STASH_TYPE_BOTTOM
else -> STASH_TYPE_NONE else -> STASH_TYPE_NONE
} }
} }
@@ -368,57 +373,69 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
val areasOverlappingPipX = keepClearAreas.filter { it.intersectsX(bounds) } val areasOverlappingPipX = keepClearAreas.filter { it.intersectsX(bounds) }
val areasOverlappingPipY = keepClearAreas.filter { it.intersectsY(bounds) } val areasOverlappingPipY = keepClearAreas.filter { it.intersectsY(bounds) }
if (areasOverlappingPipX.isNotEmpty()) {
if (screenBounds.bottom - bounds.bottom <= bounds.top - screenBounds.top) { if (screenBounds.bottom - bounds.bottom <= bounds.top - screenBounds.top) {
val fullStashTop = screenBounds.bottom - stashOffset val fullStashTop = screenBounds.bottom - stashOffset
val maxBottom = areasOverlappingPipX.maxByOrNull { it.bottom }!!.bottom val maxBottom = areasOverlappingPipX.maxByOrNull { it.bottom }!!.bottom
val partialStashTop = maxBottom + pipAreaPadding val partialStashTop = maxBottom + pipAreaPadding
val newTop = min(fullStashTop, partialStashTop)
if (newTop > bounds.top) {
val downPosition = Rect(bounds) val downPosition = Rect(bounds)
downPosition.offsetTo(bounds.left, min(fullStashTop, partialStashTop)) downPosition.offsetTo(bounds.left, newTop)
stashCandidates += downPosition stashCandidates += downPosition
} }
}
if (screenBounds.bottom - bounds.bottom >= bounds.top - screenBounds.top) { if (screenBounds.bottom - bounds.bottom >= bounds.top - screenBounds.top) {
val fullStashBottom = screenBounds.top - bounds.height() + stashOffset val fullStashBottom = screenBounds.top - bounds.height() + stashOffset
val minTop = areasOverlappingPipX.minByOrNull { it.top }!!.top val minTop = areasOverlappingPipX.minByOrNull { it.top }!!.top
val partialStashBottom = minTop - bounds.height() - pipAreaPadding val partialStashBottom = minTop - bounds.height() - pipAreaPadding
val newTop = max(fullStashBottom, partialStashBottom)
if (newTop < bounds.top) {
val upPosition = Rect(bounds) val upPosition = Rect(bounds)
upPosition.offsetTo(bounds.left, max(fullStashBottom, partialStashBottom)) upPosition.offsetTo(bounds.left, newTop)
stashCandidates += upPosition stashCandidates += upPosition
} }
}
}
if (areasOverlappingPipY.isNotEmpty()) {
if (screenBounds.right - bounds.right <= bounds.left - screenBounds.left) { if (screenBounds.right - bounds.right <= bounds.left - screenBounds.left) {
val fullStashRight = screenBounds.right - stashOffset val fullStashRight = screenBounds.right - stashOffset
val maxRight = areasOverlappingPipY.maxByOrNull { it.right }!!.right val maxRight = areasOverlappingPipY.maxByOrNull { it.right }!!.right
val partialStashRight = maxRight + pipAreaPadding val partialStashRight = maxRight + pipAreaPadding
val newLeft = min(fullStashRight, partialStashRight)
if (newLeft > bounds.left) {
val rightPosition = Rect(bounds) val rightPosition = Rect(bounds)
rightPosition.offsetTo(min(fullStashRight, partialStashRight), bounds.top) rightPosition.offsetTo(newLeft, bounds.top)
stashCandidates += rightPosition stashCandidates += rightPosition
} }
}
if (screenBounds.right - bounds.right >= bounds.left - screenBounds.left) { if (screenBounds.right - bounds.right >= bounds.left - screenBounds.left) {
val fullStashLeft = screenBounds.left - bounds.width() + stashOffset val fullStashLeft = screenBounds.left - bounds.width() + stashOffset
val minLeft = areasOverlappingPipY.minByOrNull { it.left }!!.left val minLeft = areasOverlappingPipY.minByOrNull { it.left }!!.left
val partialStashLeft = minLeft - bounds.width() - pipAreaPadding val partialStashLeft = minLeft - bounds.width() - pipAreaPadding
val newLeft = max(fullStashLeft, partialStashLeft)
if (newLeft < bounds.left) {
val leftPosition = Rect(bounds) val leftPosition = Rect(bounds)
leftPosition.offsetTo(max(fullStashLeft, partialStashLeft), bounds.top) leftPosition.offsetTo(newLeft, bounds.top)
stashCandidates += leftPosition stashCandidates += leftPosition
} }
}
if (stashCandidates.isEmpty()) {
return bounds
} }
return stashCandidates.minByOrNull { return stashCandidates.minByOrNull {
val dx = abs(it.left - bounds.left) val dx = abs(it.left - bounds.left)
val dy = abs(it.top - bounds.top) val dy = abs(it.top - bounds.top)
return@minByOrNull dx + dy return@minByOrNull dx + dy
}!! } ?: bounds
} }
/** /**
@@ -768,7 +785,7 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
} }
/** /**
* Adds space around [size] to leave space for decorations that will be drawn around the pip * Adds space around [size] to leave space for decorations that will be drawn around the PiP
*/ */
private fun addDecors(size: Size): Size { private fun addDecors(size: Size): Size {
val bounds = Rect(0, 0, size.width, size.height) val bounds = Rect(0, 0, size.width, size.height)
@@ -779,7 +796,7 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
} }
/** /**
* Removes the space that was reserved for permanent decorations around the pip * Removes the space that was reserved for permanent decorations around the PiP
* @param bounds the bounds (in screen space) to remove the insets from * @param bounds the bounds (in screen space) to remove the insets from
*/ */
private fun removePermanentDecors(bounds: Rect): Rect { private fun removePermanentDecors(bounds: Rect): Rect {
@@ -789,19 +806,20 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
} }
/** /**
* Removes the space that was reserved for temporary decorations around the pip * Removes the space that was reserved for temporary decorations around the PiP
* @param bounds the bounds (in base case) to remove the insets from * @param bounds the bounds (in base case) to remove the insets from
*/ */
private fun removeTemporaryDecorsTransformed(bounds: Rect): Rect { private fun removeTemporaryDecorsTransformed(bounds: Rect): Rect {
if (pipTemporaryDecorInsets == Insets.NONE) return bounds if (pipTemporaryDecorInsets == Insets.NONE) return bounds
var reverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets) val reverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets)
var boundsInScreenSpace = fromTransformedSpace(bounds) val boundsInScreenSpace = fromTransformedSpace(bounds)
boundsInScreenSpace.inset(reverseInsets) boundsInScreenSpace.inset(reverseInsets)
return toTransformedSpace(boundsInScreenSpace) return toTransformedSpace(boundsInScreenSpace)
} }
private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) } private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) }
private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right
private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
private fun Rect.intersects(other: Rect) = intersectsX(other) && intersectsY(other)
} }

View File

@@ -25,6 +25,7 @@ import org.junit.runner.RunWith
import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@@ -433,6 +434,28 @@ class TvPipKeepClearAlgorithmTest {
assertEquals(currentTime + algorithm.stashDuration, placement.unstashTime) assertEquals(currentTime + algorithm.stashDuration, placement.unstashTime)
} }
@Test
fun test_ExpandedPiPHeightExceedsMovementBounds_AtAnchor() {
gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
pipSize = Size(DEFAULT_PIP_SIZE.width, SCREEN_SIZE.height)
testAnchorPosition()
}
@Test
fun test_ExpandedPiPHeightExceedsMovementBounds_BottomBar_StashedUp() {
gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
pipSize = Size(DEFAULT_PIP_SIZE.width, SCREEN_SIZE.height)
val bottomBar = makeBottomBar(96)
unrestrictedAreas.add(bottomBar)
val expectedBounds = getExpectedAnchorBounds()
expectedBounds.offset(0, -bottomBar.height() - PADDING)
val placement = getActualPlacement()
assertEquals(expectedBounds, placement.bounds)
assertEquals(STASH_TYPE_TOP, placement.stashType)
assertEquals(getExpectedAnchorBounds(), placement.unstashDestinationBounds)
}
@Test @Test
fun test_PipInsets() { fun test_PipInsets() {
val permInsets = Insets.of(-1, -2, -3, -4) val permInsets = Insets.of(-1, -2, -3, -4)