Apply temporary PiP decor separately from overall placement

This change moves temporary decor handling out of the KeepClearAlgorithm
as it does not factor into keep clear area avoidance, and separating it
allows us to animate out the edu text without re-calculating the overall
PiP position. The PiP does not move immediately in response to keep
clear area changes, as it waits for the PiP position to stabilize - but
changes to temporary decor, such as edu text, should still be applied
immediately.

Bug: 226583836
Test: test TvPipBoundsControllerTest TvPipKeepClearAlgorithmTest
Change-Id: I49d32c18af13978a8a31b9a573273905fce934fd
This commit is contained in:
Robert Horvath
2022-05-03 18:02:29 +02:00
parent 5996d908e1
commit 86c660c7e8
5 changed files with 46 additions and 53 deletions

View File

@@ -102,7 +102,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
updateGravityOnExpandToggled(Gravity.NO_GRAVITY, true);
}
mTvPipBoundsState.setTvPipExpanded(isPipExpanded);
return getTvPipPlacement().getBounds();
return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
}
/** Returns the current bounds adjusted to the new aspect ratio, if valid. */
@@ -112,7 +112,20 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: getAdjustedDestinationBounds: %f", TAG, newAspectRatio);
}
return getTvPipPlacement().getBounds();
return adjustBoundsForTemporaryDecor(getTvPipPlacement().getBounds());
}
Rect adjustBoundsForTemporaryDecor(Rect bounds) {
Rect boundsWithDecor = new Rect(bounds);
Insets decorInset = mTvPipBoundsState.getPipMenuTemporaryDecorInsets();
Insets pipDecorReverseInsets = Insets.subtract(Insets.NONE, decorInset);
boundsWithDecor.inset(decorInset);
Gravity.apply(mTvPipBoundsState.getTvPipGravity(),
boundsWithDecor.width(), boundsWithDecor.height(), bounds, boundsWithDecor);
// remove temporary decoration again
boundsWithDecor.inset(pipDecorReverseInsets);
return boundsWithDecor;
}
/**
@@ -152,8 +165,6 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
mKeepClearAlgorithm.setPipPermanentDecorInsets(
mTvPipBoundsState.getPipMenuPermanentDecorInsets());
mKeepClearAlgorithm.setPipTemporaryDecorInsets(
mTvPipBoundsState.getPipMenuTemporaryDecorInsets());
final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
pipSize,

View File

@@ -62,6 +62,7 @@ public class TvPipBoundsController {
private int mResizeAnimationDuration;
private int mStashDurationMs;
private Rect mCurrentPlacementBounds;
private Rect mPipTargetBounds;
private final Runnable mApplyPendingPlacementRunnable = this::applyPendingPlacement;
@@ -96,15 +97,16 @@ public class TvPipBoundsController {
}
/**
* Update the PiP bounds based on the state of the PiP and keep clear areas.
* Unless {@code immediate} is {@code true}, the PiP does not move immediately to its new
* position, but waits for a new position to stay uncontested for
* Update the PiP bounds based on the state of the PiP, decors, and keep clear areas.
* Unless {@code immediate} is {@code true}, the PiP does not move immediately to avoid
* keep clear areas, but waits for a new position to stay uncontested for
* {@link #POSITION_DEBOUNCE_TIMEOUT_MILLIS} before moving to it.
* Temporary decor changes are applied immediately.
*
* @param stayAtAnchorPosition If true, PiP will be placed at the anchor position
* @param disallowStashing If true, PiP will not be placed off-screen in a stashed position
* @param animationDuration Duration of the animation to the new position
* @param immediate If true, PiP will move immediately
* @param immediate If true, PiP will move immediately to avoid keep clear areas
*/
@VisibleForTesting
void recalculatePipBounds(boolean stayAtAnchorPosition, boolean disallowStashing,
@@ -115,15 +117,16 @@ public class TvPipBoundsController {
mTvPipBoundsState.setStashed(stashType);
if (stayAtAnchorPosition) {
cancelScheduledPlacement();
movePipTo(placement.getAnchorBounds(), animationDuration);
applyPlacementBounds(placement.getAnchorBounds(), animationDuration);
} else if (disallowStashing) {
cancelScheduledPlacement();
movePipTo(placement.getUnstashedBounds(), animationDuration);
applyPlacementBounds(placement.getUnstashedBounds(), animationDuration);
} else if (immediate) {
cancelScheduledPlacement();
movePipTo(placement.getBounds(), animationDuration);
applyPlacementBounds(placement.getBounds(), animationDuration);
scheduleUnstashIfNeeded(placement);
} else {
applyPlacementBounds(mCurrentPlacementBounds, animationDuration);
schedulePinnedStackPlacement(placement, animationDuration);
}
}
@@ -159,7 +162,7 @@ public class TvPipBoundsController {
}
if (placement.getUnstashDestinationBounds() != null) {
mUnstashRunnable = () -> {
movePipTo(placement.getUnstashDestinationBounds(),
applyPlacementBounds(placement.getUnstashDestinationBounds(),
mResizeAnimationDuration);
mUnstashRunnable = null;
};
@@ -180,10 +183,10 @@ public class TvPipBoundsController {
if (mUnstashRunnable != null) {
// currently stashed, use stashed pos
movePipTo(mPendingPlacement.getBounds(),
applyPlacementBounds(mPendingPlacement.getBounds(),
mPendingPlacementAnimationDuration);
} else {
movePipTo(mPendingPlacement.getUnstashedBounds(),
applyPlacementBounds(mPendingPlacement.getUnstashedBounds(),
mPendingPlacementAnimationDuration);
}
}
@@ -192,6 +195,7 @@ public class TvPipBoundsController {
}
void onPipDismissed() {
mCurrentPlacementBounds = null;
mPipTargetBounds = null;
cancelScheduledPlacement();
}
@@ -206,6 +210,16 @@ public class TvPipBoundsController {
}
}
private void applyPlacementBounds(Rect bounds, int animationDuration) {
if (bounds == null) {
return;
}
mCurrentPlacementBounds = bounds;
Rect adjustedBounds = mTvPipBoundsAlgorithm.adjustBoundsForTemporaryDecor(bounds);
movePipTo(adjustedBounds, animationDuration);
}
/** Animates the PiP to the given bounds with the given animation duration. */
private void movePipTo(Rect bounds, int animationDuration) {
if (Objects.equals(mPipTargetBounds, bounds)) {

View File

@@ -88,9 +88,6 @@ class TvPipKeepClearAlgorithm() {
/** Spaces around the PiP that we should leave space for when placing the PiP. Permanent PiP
* decorations are relevant for calculating intersecting keep clear areas */
private var pipPermanentDecorInsets = Insets.NONE
/** Spaces around the PiP that we should leave space for when placing the PiP. Temporary PiP
* decorations are not relevant for calculating intersecting keep clear areas */
private var pipTemporaryDecorInsets = Insets.NONE
/**
* Calculates the position the PiP should be placed at, taking into consideration the
@@ -120,13 +117,11 @@ class TvPipKeepClearAlgorithm() {
val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
val pipSizeWithAllDecors = addDecors(pipSize)
val pipAnchorBoundsWithAllDecors =
val pipAnchorBoundsWithDecors =
getNormalPipAnchorBounds(pipSizeWithAllDecors, transformedMovementBounds)
val pipAnchorBoundsWithPermanentDecors =
removeTemporaryDecorsTransformed(pipAnchorBoundsWithAllDecors)
val result = calculatePipPositionTransformed(
pipAnchorBoundsWithPermanentDecors,
pipAnchorBoundsWithDecors,
transformedRestrictedAreas,
transformedUnrestrictedAreas
)
@@ -461,10 +456,6 @@ class TvPipKeepClearAlgorithm() {
pipPermanentDecorInsets = insets
}
fun setPipTemporaryDecorInsets(insets: Insets) {
pipTemporaryDecorInsets = insets
}
/**
* @param open Whether this event marks the opening of an occupied segment
* @param pos The coordinate of this event
@@ -759,7 +750,6 @@ class TvPipKeepClearAlgorithm() {
private fun addDecors(size: Size): Size {
val bounds = Rect(0, 0, size.width, size.height)
bounds.inset(pipPermanentDecorInsets)
bounds.inset(pipTemporaryDecorInsets)
return Size(bounds.width(), bounds.height())
}
@@ -774,19 +764,6 @@ class TvPipKeepClearAlgorithm() {
return bounds
}
/**
* Removes the space that was reserved for temporary decorations around the PiP
* @param bounds the bounds (in base case) to remove the insets from
*/
private fun removeTemporaryDecorsTransformed(bounds: Rect): Rect {
if (pipTemporaryDecorInsets == Insets.NONE) return bounds
val reverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets)
val boundsInScreenSpace = fromTransformedSpace(bounds)
boundsInScreenSpace.inset(reverseInsets)
return toTransformedSpace(boundsInScreenSpace)
}
private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) }
private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right
private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom

View File

@@ -31,6 +31,7 @@ import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.AdditionalAnswers.returnsFirstArg
import org.mockito.Mock
import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.any
@@ -92,6 +93,8 @@ class TvPipBoundsControllerTest {
whenever(context.resources).thenReturn(resources)
whenever(resources.getInteger(R.integer.config_pipStashDuration)).thenReturn(STASH_DURATION)
whenever(tvPipBoundsAlgorithm.adjustBoundsForTemporaryDecor(any()))
.then(returnsFirstArg<Rect>())
boundsController = TvPipBoundsController(
context,

View File

@@ -453,21 +453,9 @@ class TvPipKeepClearAlgorithmTest {
@Test
fun test_PipInsets() {
val permInsets = Insets.of(-1, -2, -3, -4)
algorithm.setPipPermanentDecorInsets(permInsets)
testInsetsForAllPositions(permInsets)
val insets = Insets.of(-1, -2, -3, -4)
algorithm.setPipPermanentDecorInsets(insets)
val tempInsets = Insets.of(-4, -3, -2, -1)
algorithm.setPipPermanentDecorInsets(Insets.NONE)
algorithm.setPipTemporaryDecorInsets(tempInsets)
testInsetsForAllPositions(tempInsets)
algorithm.setPipPermanentDecorInsets(permInsets)
algorithm.setPipTemporaryDecorInsets(tempInsets)
testInsetsForAllPositions(Insets.add(permInsets, tempInsets))
}
private fun testInsetsForAllPositions(insets: Insets) {
gravity = Gravity.BOTTOM or Gravity.RIGHT
testAnchorPositionWithInsets(insets)