From 82b6802083cb01a525758edde176ceeaec9514f6 Mon Sep 17 00:00:00 2001 From: "jorgegil@google.com" Date: Mon, 16 Nov 2020 11:00:26 -0800 Subject: [PATCH 1/2] Add unit tests for PipBoundsAlgorithm Bug: 169373982 Test: atest PipBoundsAlgorithmTest Change-Id: I033039191f34e4bae22cd2690b0413cab1aaa7a5 --- .../wm/shell/pip/PipBoundsAlgorithm.java | 73 ++++++++- .../wm/shell/pip/PipSnapAlgorithm.java | 73 --------- .../wm/shell/pip/phone/PipTouchHandler.java | 2 +- .../wm/shell/pip/PipBoundsAlgorithmTest.java | 148 +++++++++++++++++- .../shell/pip/phone/PipTouchHandlerTest.java | 4 +- 5 files changed, 217 insertions(+), 83 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index df6683ebb80b2..9b739932f9de7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -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. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java index 71060752df09e..d6dc536b9e365 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java @@ -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}. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 1c5d5b8a82628..37d7e51519271 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -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())); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java index 7a6e0c1b41fc3..a65d832359d2f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java @@ -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); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index b25c74d128186..c00210e57398c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -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()); From cb5acc2f2bbc6322ed78bdf2832bb6e49471d64b Mon Sep 17 00:00:00 2001 From: "jorgegil@google.com" Date: Wed, 18 Nov 2020 11:10:30 -0800 Subject: [PATCH 2/2] Add unit tests for PipSnapAlgorithm Bug: 169373982 Test: com.android.shell.wm.pip Change-Id: Iecc7ede73644f975c8bc71300e72925bd220f28d --- .../wm/shell/pip/PipBoundsAlgorithm.java | 17 +- .../wm/shell/pip/PipSnapAlgorithm.java | 20 +- .../wm/shell/pip/phone/PipTouchHandler.java | 17 +- .../wm/shell/pip/PipSnapAlgorithmTest.java | 237 ++++++++++++++++++ .../shell/pip/phone/PipTouchHandlerTest.java | 6 +- 5 files changed, 269 insertions(+), 28 deletions(-) create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index 9b739932f9de7..1bb5eda25058f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -279,12 +279,27 @@ public class PipBoundsAlgorithm { getInsetBounds(movementBounds); // Apply the movement bounds adjustments based on the current state. - mSnapAlgorithm.getMovementBounds(stackBounds, movementBounds, movementBounds, + getMovementBounds(stackBounds, movementBounds, movementBounds, (adjustForIme && mPipBoundsState.isImeShowing()) ? mPipBoundsState.getImeHeight() : 0); + return movementBounds; } + /** + * Adjusts movementBoundsOut so that it is the movement bounds for the given stackBounds. + */ + public void getMovementBounds(Rect stackBounds, Rect insetBounds, Rect movementBoundsOut, + int bottomOffset) { + // Adjust the right/bottom to ensure the stack bounds never goes offscreen + movementBoundsOut.set(insetBounds); + movementBoundsOut.right = Math.max(insetBounds.left, insetBounds.right + - stackBounds.width()); + movementBoundsOut.bottom = Math.max(insetBounds.top, insetBounds.bottom + - stackBounds.height()); + movementBoundsOut.bottom -= bottomOffset; + } + /** * @return the default snap fraction to apply instead of the default gravity when calculating * the default stack bounds when first entering PiP. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java index d6dc536b9e365..0528e4d88374d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java @@ -22,6 +22,8 @@ import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT; import android.graphics.Rect; +import com.android.internal.annotations.VisibleForTesting; + /** * Calculates the snap targets and the snap position for the PIP given a position and a velocity. * All bounds are relative to the display top/left. @@ -118,26 +120,12 @@ public class PipSnapAlgorithm { } } - /** - * Adjusts {@param movementBoundsOut} so that it is the movement bounds for the given - * {@param stackBounds}. - */ - public void getMovementBounds(Rect stackBounds, Rect insetBounds, Rect movementBoundsOut, - int bottomOffset) { - // Adjust the right/bottom to ensure the stack bounds never goes offscreen - movementBoundsOut.set(insetBounds); - movementBoundsOut.right = Math.max(insetBounds.left, insetBounds.right - - stackBounds.width()); - movementBoundsOut.bottom = Math.max(insetBounds.top, insetBounds.bottom - - stackBounds.height()); - movementBoundsOut.bottom -= bottomOffset; - } - /** * Snaps the {@param stackBounds} to the closest edge of the {@param movementBounds} and writes * the new bounds out to {@param boundsOut}. */ - public void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut, + @VisibleForTesting + void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut, @PipBoundsState.StashType int stashType) { int leftEdge = stackBounds.left; if (stashType == STASH_TYPE_LEFT) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 37d7e51519271..48fa2115305da 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -307,8 +307,7 @@ public class PipTouchHandler { public void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) { final Rect toMovementBounds = new Rect(); - mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(outBounds, insetBounds, - toMovementBounds, 0); + mPipBoundsAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0); final int prevBottom = mPipBoundsState.getMovementBounds().bottom - mMovementBoundsExtraOffsets; if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) { @@ -339,13 +338,13 @@ public class PipTouchHandler { // Re-calculate the expanded bounds Rect normalMovementBounds = new Rect(); - mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(normalBounds, insetBounds, + mPipBoundsAlgorithm.getMovementBounds(normalBounds, insetBounds, normalMovementBounds, bottomOffset); if (mPipBoundsState.getMovementBounds().isEmpty()) { // mMovementBounds is not initialized yet and a clean movement bounds without // bottom offset shall be used later in this function. - mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(curBounds, insetBounds, + mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds, mPipBoundsState.getMovementBounds(), 0 /* bottomOffset */); } @@ -358,7 +357,7 @@ public class PipTouchHandler { mPipBoundsState.setExpandedBounds( new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight())); Rect expandedMovementBounds = new Rect(); - mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds( + mPipBoundsAlgorithm.getMovementBounds( mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds, bottomOffset); @@ -381,7 +380,7 @@ public class PipTouchHandler { } else { final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu(); final Rect toMovementBounds = new Rect(); - mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(curBounds, insetBounds, + mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds, toMovementBounds, mIsImeShowing ? mImeHeight : 0); final int prevBottom = mPipBoundsState.getMovementBounds().bottom - mMovementBoundsExtraOffsets; @@ -659,7 +658,7 @@ public class PipTouchHandler { private void animateToUnexpandedState(Rect restoreBounds) { Rect restoredMovementBounds = new Rect(); - mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(restoreBounds, + mPipBoundsAlgorithm.getMovementBounds(restoreBounds, mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0); mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction, restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */); @@ -865,7 +864,7 @@ public class PipTouchHandler { * resized. */ private void updateMovementBounds() { - mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(mPipBoundsState.getBounds(), + mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(), mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0); mMotionHelper.onMovementBoundsChanged(); @@ -877,7 +876,7 @@ public class PipTouchHandler { private Rect getMovementBounds(Rect curBounds) { Rect movementBounds = new Rect(); - mPipBoundsAlgorithm.getSnapAlgorithm().getMovementBounds(curBounds, mInsetBounds, + mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds, movementBounds, mIsImeShowing ? mImeHeight : 0); return movementBounds; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java new file mode 100644 index 0000000000000..dcee2e1847b2a --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip; + +import static org.junit.Assert.assertEquals; + +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for {@link PipSnapAlgorithm}. **/ +@RunWith(AndroidTestingRunner.class) +@SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class PipSnapAlgorithmTest extends ShellTestCase { + private static final int DEFAULT_STASH_OFFSET = 32; + private static final Rect DISPLAY_BOUNDS = new Rect(0, 0, 2000, 2000); + private static final Rect STACK_BOUNDS_CENTERED = new Rect(900, 900, 1100, 1100); + private static final Rect MOVEMENT_BOUNDS = new Rect(0, 0, + DISPLAY_BOUNDS.width() - STACK_BOUNDS_CENTERED.width(), + DISPLAY_BOUNDS.width() - STACK_BOUNDS_CENTERED.width()); + + private PipSnapAlgorithm mPipSnapAlgorithm; + + @Before + public void setUp() { + mPipSnapAlgorithm = new PipSnapAlgorithm(); + } + + @Test + public void testApplySnapFraction_topEdge() { + final float snapFraction = 0.25f; + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + + mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction); + + assertEquals(MOVEMENT_BOUNDS.width() / 4, bounds.left); + assertEquals(MOVEMENT_BOUNDS.top, bounds.top); + } + + @Test + public void testApplySnapFraction_rightEdge() { + final float snapFraction = 1.5f; + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + + mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction); + + assertEquals(MOVEMENT_BOUNDS.right, bounds.left); + assertEquals(MOVEMENT_BOUNDS.height() / 2, bounds.top); + } + + @Test + public void testApplySnapFraction_bottomEdge() { + final float snapFraction = 2.25f; + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + + mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction); + + assertEquals((int) (MOVEMENT_BOUNDS.width() * 0.75f), bounds.left); + assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top); + } + + @Test + public void testApplySnapFraction_leftEdge() { + final float snapFraction = 3.75f; + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + + mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction); + + assertEquals(MOVEMENT_BOUNDS.left, bounds.left); + assertEquals((int) (MOVEMENT_BOUNDS.height() * 0.25f), bounds.top); + } + + @Test + public void testApplySnapFraction_notStashed_isNotOffBounds() { + final float snapFraction = 2f; + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + + mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction, + PipBoundsState.STASH_TYPE_NONE, DEFAULT_STASH_OFFSET, DISPLAY_BOUNDS); + + assertEquals(MOVEMENT_BOUNDS.right, bounds.left); + assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top); + } + + @Test + public void testApplySnapFraction_stashedLeft() { + final float snapFraction = 3f; + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + + mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction, + PipBoundsState.STASH_TYPE_LEFT, DEFAULT_STASH_OFFSET, DISPLAY_BOUNDS); + + final int offBoundsWidth = bounds.width() - DEFAULT_STASH_OFFSET; + assertEquals(MOVEMENT_BOUNDS.left - offBoundsWidth, bounds.left); + assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top); + } + + @Test + public void testApplySnapFraction_stashedRight() { + final float snapFraction = 2f; + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + + mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, snapFraction, + PipBoundsState.STASH_TYPE_RIGHT, DEFAULT_STASH_OFFSET, DISPLAY_BOUNDS); + + assertEquals(DISPLAY_BOUNDS.right - DEFAULT_STASH_OFFSET, bounds.left); + assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top); + } + + @Test + public void testSnapRectToClosestEdge_rightEdge() { + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + // Move the centered rect slightly to the right side. + bounds.offset(10, 0); + + mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds, + PipBoundsState.STASH_TYPE_NONE); + + assertEquals(MOVEMENT_BOUNDS.right, bounds.left); + } + + @Test + public void testSnapRectToClosestEdge_leftEdge() { + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + // Move the centered rect slightly to the left side. + bounds.offset(-10, 0); + + mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds, + PipBoundsState.STASH_TYPE_NONE); + + assertEquals(MOVEMENT_BOUNDS.left, bounds.left); + } + + @Test + public void testSnapRectToClosestEdge_topEdge() { + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + // Move the centered rect slightly to the top half. + bounds.offset(0, -10); + + mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds, + PipBoundsState.STASH_TYPE_NONE); + + assertEquals(MOVEMENT_BOUNDS.top, bounds.top); + } + + @Test + public void testSnapRectToClosestEdge_bottomEdge() { + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + // Move the centered rect slightly to the bottom half. + bounds.offset(0, 10); + + mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds, + PipBoundsState.STASH_TYPE_NONE); + + assertEquals(MOVEMENT_BOUNDS.bottom, bounds.top); + } + + @Test + public void testSnapRectToClosestEdge_stashed_unStahesBounds() { + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + // Stash it on the left side. + mPipSnapAlgorithm.applySnapFraction(bounds, MOVEMENT_BOUNDS, 3.5f, + PipBoundsState.STASH_TYPE_LEFT, DEFAULT_STASH_OFFSET, DISPLAY_BOUNDS); + + mPipSnapAlgorithm.snapRectToClosestEdge(bounds, MOVEMENT_BOUNDS, bounds, + PipBoundsState.STASH_TYPE_LEFT); + + assertEquals(MOVEMENT_BOUNDS.left, bounds.left); + } + + @Test + public void testGetSnapFraction_leftEdge() { + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + // Move it slightly to the left side. + bounds.offset(-10, 0); + + final float snapFraction = mPipSnapAlgorithm.getSnapFraction(bounds, MOVEMENT_BOUNDS); + + assertEquals(3.5f, snapFraction, 0.1f); + } + + @Test + public void testGetSnapFraction_rightEdge() { + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + // Move it slightly to the right side. + bounds.offset(10, 0); + + final float snapFraction = mPipSnapAlgorithm.getSnapFraction(bounds, MOVEMENT_BOUNDS); + + assertEquals(1.5f, snapFraction, 0.1f); + } + + @Test + public void testGetSnapFraction_topEdge() { + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + // Move it slightly to the top half. + bounds.offset(0, -10); + + final float snapFraction = mPipSnapAlgorithm.getSnapFraction(bounds, MOVEMENT_BOUNDS); + + assertEquals(0.5f, snapFraction, 0.1f); + } + + @Test + public void testGetSnapFraction_bottomEdge() { + final Rect bounds = new Rect(STACK_BOUNDS_CENTERED); + // Move it slightly to the bottom half. + bounds.offset(0, 10); + + final float snapFraction = mPipSnapAlgorithm.getSnapFraction(bounds, MOVEMENT_BOUNDS); + + assertEquals(2.5f, snapFraction, 0.1f); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index c00210e57398c..abbc681f53fe6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -115,7 +115,8 @@ public class PipTouchHandlerTest extends ShellTestCase { @Test public void updateMovementBounds_minBounds() { Rect expectedMinMovementBounds = new Rect(); - mPipSnapAlgorithm.getMovementBounds(mMinBounds, mInsetBounds, expectedMinMovementBounds, 0); + mPipBoundsAlgorithm.getMovementBounds(mMinBounds, mInsetBounds, expectedMinMovementBounds, + 0); mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds, mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation); @@ -134,7 +135,8 @@ public class PipTouchHandlerTest extends ShellTestCase { R.dimen.pip_expanded_shortest_edge_size), displaySize.x, displaySize.y); Rect maxBounds = new Rect(0, 0, maxSize.getWidth(), maxSize.getHeight()); Rect expectedMaxMovementBounds = new Rect(); - mPipSnapAlgorithm.getMovementBounds(maxBounds, mInsetBounds, expectedMaxMovementBounds, 0); + mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, expectedMaxMovementBounds, + 0); mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds, mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);