Add unit tests for PipBoundsAlgorithm

Bug: 169373982
Test: atest PipBoundsAlgorithmTest
Change-Id: I033039191f34e4bae22cd2690b0413cab1aaa7a5
This commit is contained in:
jorgegil@google.com
2020-11-16 11:00:26 -08:00
parent 3e5f65ddcb
commit 82b6802083
5 changed files with 217 additions and 83 deletions

View File

@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.Size;
@@ -42,6 +43,9 @@ public class PipBoundsAlgorithm {
private final @NonNull PipBoundsState mPipBoundsState;
private final PipSnapAlgorithm mSnapAlgorithm;
private float mDefaultSizePercent;
private float mMinAspectRatioForMinSize;
private float mMaxAspectRatioForMinSize;
private float mDefaultAspectRatio;
private float mMinAspectRatio;
private float mMaxAspectRatio;
@@ -51,7 +55,7 @@ public class PipBoundsAlgorithm {
public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState) {
mPipBoundsState = pipBoundsState;
mSnapAlgorithm = new PipSnapAlgorithm(context);
mSnapAlgorithm = new PipSnapAlgorithm();
reloadResources(context);
// Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
// resources as it would clobber mAspectRatio when entering PiP from fullscreen which
@@ -83,6 +87,11 @@ public class PipBoundsAlgorithm {
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
mMaxAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
mDefaultSizePercent = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent);
mMaxAspectRatioForMinSize = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
}
/**
@@ -174,7 +183,7 @@ public class PipBoundsAlgorithm {
final int minEdgeSize = useCurrentMinEdgeSize ? mPipBoundsState.getMinEdgeSize()
: defaultMinEdgeSize;
// Use the existing size but adjusted to the aspect ratio and min edge size.
size = mSnapAlgorithm.getSizeForAspectRatio(
size = getSizeForAspectRatio(
new Size(stackBounds.width(), stackBounds.height()), aspectRatio, minEdgeSize);
} else {
if (overrideMinSize != null) {
@@ -184,7 +193,7 @@ public class PipBoundsAlgorithm {
} else {
// Calculate the default size using the display size and default min edge size.
final DisplayInfo displayInfo = mPipBoundsState.getDisplayInfo();
size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, mDefaultMinSize,
size = getSizeForAspectRatio(aspectRatio, mDefaultMinSize,
displayInfo.logicalWidth, displayInfo.logicalHeight);
}
}
@@ -229,7 +238,7 @@ public class PipBoundsAlgorithm {
defaultSize = adjustSizeToAspectRatio(overrideMinSize, mDefaultAspectRatio);
} else {
// Calculate the default size using the display size and default min edge size.
defaultSize = mSnapAlgorithm.getSizeForAspectRatio(mDefaultAspectRatio,
defaultSize = getSizeForAspectRatio(mDefaultAspectRatio,
mDefaultMinSize, displayInfo.logicalWidth, displayInfo.logicalHeight);
}
Gravity.apply(mDefaultStackGravity, defaultSize.getWidth(), defaultSize.getHeight(),
@@ -303,6 +312,62 @@ public class PipBoundsAlgorithm {
return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
}
/**
* @return the size of the PiP at the given aspectRatio, ensuring that the minimum edge
* is at least minEdgeSize.
*/
public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth,
int displayHeight) {
final int smallestDisplaySize = Math.min(displayWidth, displayHeight);
final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent);
final int width;
final int height;
if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) {
// Beyond these points, we can just use the min size as the shorter edge
if (aspectRatio <= 1) {
// Portrait, width is the minimum size
width = minSize;
height = Math.round(width / aspectRatio);
} else {
// Landscape, height is the minimum size
height = minSize;
width = Math.round(height * aspectRatio);
}
} else {
// Within these points, we ensure that the bounds fit within the radius of the limits
// at the points
final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
height = (int) Math.round(Math.sqrt((radius * radius)
/ (aspectRatio * aspectRatio + 1)));
width = Math.round(height * aspectRatio);
}
return new Size(width, height);
}
/**
* @return the adjusted size so that it conforms to the given aspectRatio, ensuring that the
* minimum edge is at least minEdgeSize.
*/
public Size getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize) {
final int smallestSize = Math.min(size.getWidth(), size.getHeight());
final int minSize = (int) Math.max(minEdgeSize, smallestSize);
final int width;
final int height;
if (aspectRatio <= 1) {
// Portrait, width is the minimum size.
width = minSize;
height = Math.round(width / aspectRatio);
} else {
// Landscape, height is the minimum size
height = minSize;
width = Math.round(height * aspectRatio);
}
return new Size(width, height);
}
/**
* Dumps internal states.
*/

View File

@@ -20,11 +20,7 @@ import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.Size;
/**
* Calculates the snap targets and the snap position for the PIP given a position and a velocity.
@@ -32,19 +28,6 @@ import android.util.Size;
*/
public class PipSnapAlgorithm {
private final float mDefaultSizePercent;
private final float mMinAspectRatioForMinSize;
private final float mMaxAspectRatioForMinSize;
public PipSnapAlgorithm(Context context) {
Resources res = context.getResources();
mDefaultSizePercent = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent);
mMaxAspectRatioForMinSize = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
}
/**
* Returns a fraction that describes where the PiP bounds is.
* See {@link #getSnapFraction(Rect, Rect, int)}.
@@ -150,62 +133,6 @@ public class PipSnapAlgorithm {
movementBoundsOut.bottom -= bottomOffset;
}
/**
* @return the size of the PiP at the given {@param aspectRatio}, ensuring that the minimum edge
* is at least {@param minEdgeSize}.
*/
public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth,
int displayHeight) {
final int smallestDisplaySize = Math.min(displayWidth, displayHeight);
final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent);
final int width;
final int height;
if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) {
// Beyond these points, we can just use the min size as the shorter edge
if (aspectRatio <= 1) {
// Portrait, width is the minimum size
width = minSize;
height = Math.round(width / aspectRatio);
} else {
// Landscape, height is the minimum size
height = minSize;
width = Math.round(height * aspectRatio);
}
} else {
// Within these points, we ensure that the bounds fit within the radius of the limits
// at the points
final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
height = (int) Math.round(Math.sqrt((radius * radius) /
(aspectRatio * aspectRatio + 1)));
width = Math.round(height * aspectRatio);
}
return new Size(width, height);
}
/**
* @return the adjusted size so that it conforms to the given aspectRatio, ensuring that the
* minimum edge is at least minEdgeSize.
*/
public Size getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize) {
final int smallestSize = Math.min(size.getWidth(), size.getHeight());
final int minSize = (int) Math.max(minEdgeSize, smallestSize);
final int width;
final int height;
if (aspectRatio <= 1) {
// Portrait, width is the minimum size.
width = minSize;
height = Math.round(width / aspectRatio);
} else {
// Landscape, height is the minimum size
height = minSize;
width = Math.round(height * aspectRatio);
}
return new Size(width, height);
}
/**
* Snaps the {@param stackBounds} to the closest edge of the {@param movementBounds} and writes
* the new bounds out to {@param boundsOut}.

View File

@@ -353,7 +353,7 @@ public class PipTouchHandler {
float aspectRatio = (float) normalBounds.width() / normalBounds.height();
Point displaySize = new Point();
mContext.getDisplay().getRealSize(displaySize);
Size expandedSize = mPipBoundsAlgorithm.getSnapAlgorithm().getSizeForAspectRatio(
Size expandedSize = mPipBoundsAlgorithm.getSizeForAspectRatio(
aspectRatio, mExpandedShortestEdgeSize, displaySize.x, displaySize.y);
mPipBoundsState.setExpandedBounds(
new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));

View File

@@ -50,6 +50,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
private static final float DEFAULT_ASPECT_RATIO = 1f;
private static final float MIN_ASPECT_RATIO = 0.5f;
private static final float MAX_ASPECT_RATIO = 2f;
private static final int DEFAULT_MIN_EDGE_SIZE = 100;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private DisplayInfo mDefaultDisplayInfo;
@@ -73,7 +74,8 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
com.android.internal.R.integer.config_defaultPictureInPictureGravity,
Gravity.END | Gravity.BOTTOM);
res.addOverride(
com.android.internal.R.dimen.default_minimal_size_pip_resizable_task, 100);
com.android.internal.R.dimen.default_minimal_size_pip_resizable_task,
DEFAULT_MIN_EDGE_SIZE);
res.addOverride(
com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets,
"16x16");
@@ -111,6 +113,127 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
ASPECT_RATIO_ERROR_MARGIN);
}
@Test
public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() {
final Size defaultSize = mPipBoundsAlgorithm.getSizeForAspectRatio(DEFAULT_ASPECT_RATIO,
DEFAULT_MIN_EDGE_SIZE, mDefaultDisplayInfo.logicalWidth,
mDefaultDisplayInfo.logicalHeight);
mPipBoundsState.setOverrideMinSize(null);
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
assertEquals(defaultSize, new Size(defaultBounds.width(), defaultBounds.height()));
assertEquals(DEFAULT_ASPECT_RATIO, getRectAspectRatio(defaultBounds),
ASPECT_RATIO_ERROR_MARGIN);
}
@Test
public void getDefaultBounds_widerOverrideMinSize_matchesMinSizeWidthAndDefaultAspectRatio() {
overrideDefaultAspectRatio(1.0f);
// The min size's aspect ratio is greater than the default aspect ratio.
final Size overrideMinSize = new Size(150, 120);
mPipBoundsState.setOverrideMinSize(overrideMinSize);
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
// The default aspect ratio should trump the min size aspect ratio.
assertEquals(DEFAULT_ASPECT_RATIO, getRectAspectRatio(defaultBounds),
ASPECT_RATIO_ERROR_MARGIN);
// The width of the min size is still used with the default aspect ratio.
assertEquals(overrideMinSize.getWidth(), defaultBounds.width());
}
@Test
public void getDefaultBounds_tallerOverrideMinSize_matchesMinSizeHeightAndDefaultAspectRatio() {
overrideDefaultAspectRatio(1.0f);
// The min size's aspect ratio is greater than the default aspect ratio.
final Size overrideMinSize = new Size(120, 150);
mPipBoundsState.setOverrideMinSize(overrideMinSize);
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
// The default aspect ratio should trump the min size aspect ratio.
assertEquals(DEFAULT_ASPECT_RATIO, getRectAspectRatio(defaultBounds),
ASPECT_RATIO_ERROR_MARGIN);
// The height of the min size is still used with the default aspect ratio.
assertEquals(overrideMinSize.getHeight(), defaultBounds.height());
}
@Test
public void getDefaultBounds_imeShowing_offsetByImeHeight() {
final int imeHeight = 30;
mPipBoundsState.setImeVisibility(false, 0);
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
mPipBoundsState.setImeVisibility(true, imeHeight);
final Rect defaultBoundsWithIme = mPipBoundsAlgorithm.getDefaultBounds();
assertEquals(imeHeight, defaultBounds.top - defaultBoundsWithIme.top);
}
@Test
public void getDefaultBounds_shelfShowing_offsetByShelfHeight() {
final int shelfHeight = 30;
mPipBoundsState.setShelfVisibility(false, 0);
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
mPipBoundsState.setShelfVisibility(true, shelfHeight);
final Rect defaultBoundsWithShelf = mPipBoundsAlgorithm.getDefaultBounds();
assertEquals(shelfHeight, defaultBounds.top - defaultBoundsWithShelf.top);
}
@Test
public void getDefaultBounds_imeAndShelfShowing_offsetByTallest() {
final int imeHeight = 30;
final int shelfHeight = 40;
mPipBoundsState.setImeVisibility(false, 0);
mPipBoundsState.setShelfVisibility(false, 0);
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
mPipBoundsState.setImeVisibility(true, imeHeight);
mPipBoundsState.setShelfVisibility(true, shelfHeight);
final Rect defaultBoundsWithIme = mPipBoundsAlgorithm.getDefaultBounds();
assertEquals(shelfHeight, defaultBounds.top - defaultBoundsWithIme.top);
}
@Test
public void getDefaultBounds_boundsAtDefaultGravity() {
final Rect insetBounds = new Rect();
mPipBoundsAlgorithm.getInsetBounds(insetBounds);
overrideDefaultStackGravity(Gravity.END | Gravity.BOTTOM);
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
assertEquals(insetBounds.bottom, defaultBounds.bottom);
assertEquals(insetBounds.right, defaultBounds.right);
}
@Test
public void getNormalBounds_invalidAspectRatio_returnsDefaultBounds() {
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
// Set an invalid current aspect ratio.
mPipBoundsState.setAspectRatio(MIN_ASPECT_RATIO / 2);
final Rect normalBounds = mPipBoundsAlgorithm.getNormalBounds();
assertEquals(defaultBounds, normalBounds);
}
@Test
public void getNormalBounds_validAspectRatio_returnsAdjustedDefaultBounds() {
final Rect defaultBoundsAdjustedToAspectRatio = mPipBoundsAlgorithm.getDefaultBounds();
mPipBoundsAlgorithm.transformBoundsToAspectRatio(defaultBoundsAdjustedToAspectRatio,
MIN_ASPECT_RATIO, false /* useCurrentMinEdgeSize */, false /* useCurrentSize */);
// Set a valid current aspect ratio different that the default.
mPipBoundsState.setAspectRatio(MIN_ASPECT_RATIO);
final Rect normalBounds = mPipBoundsAlgorithm.getNormalBounds();
assertEquals(defaultBoundsAdjustedToAspectRatio, normalBounds);
}
@Test
public void getEntryDestinationBounds_returnBoundsMatchesAspectRatio() {
final float[] aspectRatios = new float[] {
@@ -121,8 +244,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
for (float aspectRatio : aspectRatios) {
mPipBoundsState.setAspectRatio(aspectRatio);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final float actualAspectRatio =
destinationBounds.width() / (destinationBounds.height() * 1f);
final float actualAspectRatio = getRectAspectRatio(destinationBounds);
assertEquals("Destination bounds matches the given aspect ratio",
aspectRatio, actualAspectRatio, ASPECT_RATIO_ERROR_MARGIN);
}
@@ -274,6 +396,22 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
assertBoundsInclusionWithMargin("useDefaultBounds", defaultBounds, actualBounds);
}
private void overrideDefaultAspectRatio(float aspectRatio) {
final TestableResources res = mContext.getOrCreateTestableResources();
res.addOverride(
com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio,
aspectRatio);
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
}
private void overrideDefaultStackGravity(int stackGravity) {
final TestableResources res = mContext.getOrCreateTestableResources();
res.addOverride(
com.android.internal.R.integer.config_defaultPictureInPictureGravity,
stackGravity);
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
}
private void assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual) {
final Rect expectedWithMargin = new Rect(expected);
expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN);
@@ -282,4 +420,8 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
+ " with error margin " + ROUNDING_ERROR_MARGIN,
expectedWithMargin.contains(actual));
}
private static float getRectAspectRatio(Rect rect) {
return rect.width() / (rect.height() * 1f);
}
}

View File

@@ -91,7 +91,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
mPipBoundsState = new PipBoundsState(mContext);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState);
mPipSnapAlgorithm = mPipBoundsAlgorithm.getSnapAlgorithm();
mPipSnapAlgorithm = new PipSnapAlgorithm(mContext);
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipTouchHandler = new PipTouchHandler(mContext, mPipMenuActivityController,
mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer,
mFloatingContentCoordinator, mPipUiEventLogger);
@@ -129,7 +129,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
public void updateMovementBounds_maxBounds() {
Point displaySize = new Point();
mContext.getDisplay().getRealSize(displaySize);
Size maxSize = mPipSnapAlgorithm.getSizeForAspectRatio(1,
Size maxSize = mPipBoundsAlgorithm.getSizeForAspectRatio(1,
mContext.getResources().getDimensionPixelSize(
R.dimen.pip_expanded_shortest_edge_size), displaySize.x, displaySize.y);
Rect maxBounds = new Rect(0, 0, maxSize.getWidth(), maxSize.getHeight());