Merge "Unify Aspect Ratio Logic in ActivityRecord" into sc-v2-dev

This commit is contained in:
Shivam Agrawal
2021-08-03 20:15:55 +00:00
committed by Android (Google) Code Review
3 changed files with 174 additions and 84 deletions

View File

@@ -702,8 +702,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
private boolean mInSizeCompatModeForBounds = false;
// Whether the aspect ratio restrictions applied to the activity bounds in applyAspectRatio().
// TODO(b/182268157): Aspect ratio can also be applie in resolveFixedOrientationConfiguration
// but that isn't reflected in this boolean.
private boolean mIsAspectRatioApplied = false;
// Bounds populated in resolveFixedOrientationConfiguration when this activity is letterboxed
@@ -7504,11 +7502,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* requested orientation. If not, it may be necessary to letterbox the window.
* @param parentBounds are the new parent bounds passed down to the activity and should be used
* to compute the stable bounds.
* @param outBounds will store the stable bounds, which are the bounds with insets applied.
* These should be used to compute letterboxed bounds if orientation is not
* respected when insets are applied.
* @param outStableBounds will store the stable bounds, which are the bounds with insets
* applied, if orientation is not respected when insets are applied.
* Otherwise outStableBounds will be empty. Stable bounds should be used
* to compute letterboxed bounds if orientation is not respected when
* insets are applied.
*/
private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outBounds) {
private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds) {
outStableBounds.setEmpty();
if (mDisplayContent == null) {
return true;
}
@@ -7523,17 +7524,21 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Compute orientation from stable parent bounds (= parent bounds with insets applied)
final Task task = getTask();
task.calculateInsetFrames(mTmpOutNonDecorBounds /* outNonDecorBounds */,
outBounds /* outStableBounds */, parentBounds /* bounds */,
outStableBounds /* outStableBounds */, parentBounds /* bounds */,
mDisplayContent.getDisplayInfo());
final int orientationWithInsets = outBounds.height() >= outBounds.width()
final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// If orientation does not match the orientation with insets applied, then a
// display rotation will not be enough to respect orientation. However, even if they do
// not match but the orientation with insets applied matches the requested orientation, then
// there is no need to modify the bounds because when insets are applied, the activity will
// have the desired orientation.
return orientation == orientationWithInsets
final boolean orientationRespectedWithInsets = orientation == orientationWithInsets
|| orientationWithInsets == requestedOrientation;
if (orientationRespectedWithInsets) {
outStableBounds.setEmpty();
}
return orientationRespectedWithInsets;
}
/**
@@ -7551,9 +7556,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
int windowingMode) {
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
final Rect containerBounds = new Rect(parentBounds);
final Rect stableBounds = new Rect();
// If orientation is respected when insets are applied, then stableBounds will be empty.
boolean orientationRespectedWithInsets =
orientationRespectedWithInsets(parentBounds, containerBounds);
orientationRespectedWithInsets(parentBounds, stableBounds);
if (handlesOrientationChangeFromDescendant() && orientationRespectedWithInsets) {
// No need to letterbox because of fixed orientation. Display will handle
// fixed-orientation requests and a display rotation is enough to respect requested
@@ -7590,71 +7596,68 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return;
}
// TODO(b/182268157) merge aspect ratio logic here and in
// {@link ActivityRecord#applyAspectRatio}
// if no aspect ratio constraints are provided, parent aspect ratio is used
float aspectRatio = computeAspectRatio(parentBounds);
// TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app
// bounds or stable bounds to unify aspect ratio logic.
final Rect parentBoundsWithInsets = orientationRespectedWithInsets
? newParentConfig.windowConfiguration.getAppBounds() : stableBounds;
final Rect containingBounds = new Rect();
final Rect containingBoundsWithInsets = new Rect();
// Need to shrink the containing bounds into a square because the parent orientation
// does not match the activity requested orientation.
if (forcedOrientation == ORIENTATION_LANDSCAPE) {
// Landscape is defined as width > height. Make the container respect landscape
// orientation by shrinking height to one less than width. Landscape activity will be
// vertically centered within parent bounds with insets, so position vertical bounds
// within parent bounds with insets to prevent insets from unnecessarily trimming
// vertical bounds.
final int bottom = Math.min(parentBoundsWithInsets.top + parentBounds.width() - 1,
parentBoundsWithInsets.bottom);
containingBounds.set(parentBounds.left, parentBoundsWithInsets.top, parentBounds.right,
bottom);
containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top,
parentBoundsWithInsets.right, bottom);
} else {
// Portrait is defined as width <= height. Make the container respect portrait
// orientation by shrinking width to match height. Portrait activity will be
// horizontally centered within parent bounds with insets, so position horizontal bounds
// within parent bounds with insets to prevent insets from unnecessarily trimming
// horizontal bounds.
final int right = Math.min(parentBoundsWithInsets.left + parentBounds.height(),
parentBoundsWithInsets.right);
containingBounds.set(parentBoundsWithInsets.left, parentBounds.top, right,
parentBounds.bottom);
containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top,
right, parentBoundsWithInsets.bottom);
}
// Store the current bounds to be able to revert to size compat mode values below if needed.
final Rect prevResolvedBounds = new Rect(resolvedBounds);
resolvedBounds.set(containingBounds);
// Override from config_fixedOrientationLetterboxAspectRatio or via ADB with
// set-fixed-orientation-letterbox-aspect-ratio.
final float letterboxAspectRatioOverride =
mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
aspectRatio = letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
? letterboxAspectRatioOverride : aspectRatio;
final float desiredAspectRatio =
letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
? letterboxAspectRatioOverride : computeAspectRatio(parentBounds);
// Apply aspect ratio to resolved bounds
mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets,
containingBounds, desiredAspectRatio, true);
// Adjust the fixed orientation letterbox bounds to fit the app request aspect ratio in
// order to use the extra available space.
final float maxAspectRatio = info.getMaxAspectRatio();
final float minAspectRatio = info.getMinAspectRatio();
if (aspectRatio > maxAspectRatio && maxAspectRatio != 0) {
aspectRatio = maxAspectRatio;
} else if (aspectRatio < minAspectRatio) {
aspectRatio = minAspectRatio;
}
// Store the current bounds to be able to revert to size compat mode values below if needed.
final Rect prevResolvedBounds = new Rect(resolvedBounds);
// Compute other dimension based on aspect ratio. Use bounds intersected with insets, stored
// in containerBounds after calling {@link ActivityRecord#orientationRespectedWithInsets()},
// to ensure that aspect ratio is respected after insets are applied.
int activityWidth;
int activityHeight;
// Vertically center if orientation is landscape. Center within parent bounds with insets
// to ensure that insets do not trim height. Bounds will later be horizontally centered in
// {@link updateResolvedBoundsHorizontalPosition()} regardless of orientation.
if (forcedOrientation == ORIENTATION_LANDSCAPE) {
activityWidth = parentBounds.width();
// Compute height from stable bounds width to ensure orientation respected after insets.
activityHeight = (int) Math.rint(containerBounds.width() / aspectRatio);
// Landscape is defined as width > height. To ensure activity is landscape when aspect
// ratio is close to 1, reduce the height by one pixel.
if (activityWidth == activityHeight) {
activityHeight -= 1;
}
// Center vertically within stable bounds in landscape to ensure insets do not trim
// height.
final int top = containerBounds.centerY() - activityHeight / 2;
resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + activityHeight);
} else {
activityHeight = parentBounds.height();
// Compute width from stable bounds height to ensure orientation respected after insets.
activityWidth = (int) Math.rint(containerBounds.height() / aspectRatio);
// Center horizontally in portrait. For now, align to left and allow
// {@link ActivityRecord#updateResolvedBoundsHorizontalPosition()} to center
// horizontally. Exclude left insets from parent to ensure cutout does not trim width.
final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds();
resolvedBounds.set(parentAppBounds.left, parentBounds.top,
parentAppBounds.left + activityWidth, parentBounds.bottom);
final int offsetY = parentBoundsWithInsets.centerY() - resolvedBounds.centerY();
resolvedBounds.offset(0, offsetY);
}
if (mCompatDisplayInsets != null) {
mCompatDisplayInsets.getBoundsByRotation(
mTmpBounds, newParentConfig.windowConfiguration.getRotation());
// Insets may differ between different rotations, for example in the case of a display
// cutout. To ensure consistent bounds across rotations, compare the activity dimensions
// minus insets from the rotation the compat bounds were computed in.
Task.intersectWithInsetsIfFits(mTmpBounds, parentBounds,
mCompatDisplayInsets.mStableInsets[mCompatDisplayInsets.mOriginalRotation]);
if (activityWidth != mTmpBounds.width()
|| activityHeight != mTmpBounds.height()) {
if (resolvedBounds.width() != mTmpBounds.width()
|| resolvedBounds.height() != mTmpBounds.height()) {
// The app shouldn't be resized, we only do fixed orientation letterboxing if the
// compat bounds are also from the same fixed orientation letterbox. Otherwise,
// clear the fixed orientation bounds to show app in size compat mode.
@@ -7690,8 +7693,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// then they should be aligned later in #updateResolvedBoundsHorizontalPosition().
if (!mTmpBounds.isEmpty()) {
resolvedBounds.set(mTmpBounds);
// Exclude the horizontal decor area.
resolvedBounds.left = parentAppBounds.left;
}
if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
// Compute the configuration based on the resolved bounds. If aspect ratio doesn't
@@ -7752,13 +7753,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mIsAspectRatioApplied =
applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
}
// If the bounds are restricted by fixed aspect ratio, the resolved bounds should be put in
// the container app bounds. Otherwise the entire container bounds are available.
final boolean fillContainer = resolvedBounds.equals(containingBounds);
if (!fillContainer) {
// The horizontal position should not cover insets.
resolvedBounds.left = containingAppBounds.left;
}
// Use resolvedBounds to compute other override configurations such as appBounds. The bounds
// are calculated in compat container space. The actual position on screen will be applied
@@ -7825,6 +7819,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor
// if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition.
// Above coordinates are in "@" space, now place "*" and "#" to screen space.
final boolean fillContainer = resolvedBounds.equals(containingBounds);
final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
final int screenPosY = containerBounds.top;
if (screenPosX != 0 || screenPosY != 0) {
@@ -8066,6 +8061,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return true;
}
private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
Rect containingBounds) {
return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
0 /* desiredAspectRatio */, false /* fixedOrientationLetterboxed */);
}
/**
* Applies aspect ratio restrictions to outBounds. If no restrictions, then no change is
* made to outBounds.
@@ -8074,17 +8075,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
Rect containingBounds) {
Rect containingBounds, float desiredAspectRatio, boolean fixedOrientationLetterboxed) {
final float maxAspectRatio = info.getMaxAspectRatio();
final Task rootTask = getRootTask();
final float minAspectRatio = info.getMinAspectRatio();
if (task == null || rootTask == null
|| (inMultiWindowMode() && !shouldCreateCompatDisplayInsets())
|| (maxAspectRatio == 0 && minAspectRatio == 0)
|| (inMultiWindowMode() && !shouldCreateCompatDisplayInsets()
&& !fixedOrientationLetterboxed)
|| (maxAspectRatio < 1 && minAspectRatio < 1 && desiredAspectRatio < 1)
|| isInVrUiMode(getConfiguration())) {
// We don't enforce aspect ratio if the activity task is in multiwindow unless it
// is in size-compat mode. We also don't set it if we are in VR mode.
// We don't enforce aspect ratio if the activity task is in multiwindow unless it is in
// size-compat mode or is letterboxed from fixed orientation. We also don't set it if we
// are in VR mode.
return false;
}
@@ -8092,20 +8095,30 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final int containingAppHeight = containingAppBounds.height();
final float containingRatio = computeAspectRatio(containingAppBounds);
if (desiredAspectRatio < 1) {
desiredAspectRatio = containingRatio;
}
if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
desiredAspectRatio = maxAspectRatio;
} else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
desiredAspectRatio = minAspectRatio;
}
int activityWidth = containingAppWidth;
int activityHeight = containingAppHeight;
if (containingRatio > maxAspectRatio && maxAspectRatio != 0) {
if (containingRatio > desiredAspectRatio) {
if (containingAppWidth < containingAppHeight) {
// Width is the shorter side, so we use that to figure-out what the max. height
// should be given the aspect ratio.
activityHeight = (int) ((activityWidth * maxAspectRatio) + 0.5f);
activityHeight = (int) ((activityWidth * desiredAspectRatio) + 0.5f);
} else {
// Height is the shorter side, so we use that to figure-out what the max. width
// should be given the aspect ratio.
activityWidth = (int) ((activityHeight * maxAspectRatio) + 0.5f);
activityWidth = (int) ((activityHeight * desiredAspectRatio) + 0.5f);
}
} else if (containingRatio < minAspectRatio) {
} else if (containingRatio < desiredAspectRatio) {
boolean adjustWidth;
switch (getRequestedConfigurationOrientation()) {
case ORIENTATION_LANDSCAPE:
@@ -8133,9 +8146,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
break;
}
if (adjustWidth) {
activityWidth = (int) ((activityHeight / minAspectRatio) + 0.5f);
activityWidth = (int) ((activityHeight / desiredAspectRatio) + 0.5f);
} else {
activityHeight = (int) ((activityWidth / minAspectRatio) + 0.5f);
activityHeight = (int) ((activityWidth / desiredAspectRatio) + 0.5f);
}
}
@@ -8159,6 +8172,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
outBounds.set(containingBounds.left, containingBounds.top, right, bottom);
// If the bounds are restricted by fixed aspect ratio, then out bounds should be put in the
// container app bounds. Otherwise the entire container bounds are available.
if (!outBounds.equals(containingBounds)) {
// The horizontal position should not cover insets (e.g. display cutout).
outBounds.left = containingAppBounds.left;
}
return true;
}

View File

@@ -1986,6 +1986,61 @@ public class SizeCompatTests extends WindowTestsBase {
assertTrue(mActivity.areBoundsLetterboxed());
}
/**
* Tests that all three paths in which aspect ratio logic can be applied yield the same
* result, which is that aspect ratio is respected on app bounds. The three paths are
* fixed orientation, no fixed orientation but fixed aspect ratio, and size compat mode.
*/
@Test
public void testAllAspectRatioLogicConsistent() {
// Create display that has all stable insets and does not rotate. Make sure that status bar
// height is greater than notch height so that stable bounds do not equal app bounds.
final int notchHeight = 75;
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
.setSystemDecorations(true).setNotch(notchHeight)
.setStatusBarHeight(notchHeight + 20).setCanRotate(false).build();
// Create task on test display.
final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
// Target min aspect ratio must be larger than parent aspect ratio to be applied.
final float targetMinAspectRatio = 3.0f;
// Create fixed portait activity with min aspect ratio greater than parent aspect ratio.
final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
.setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.setMinAspectRatio(targetMinAspectRatio).build();
final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
.windowConfiguration.getAppBounds());
// Create activity with no fixed orientation and min aspect ratio greater than parent aspect
// ratio.
final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm).setTask(task)
.setMinAspectRatio(targetMinAspectRatio).build();
final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
.windowConfiguration.getAppBounds());
// Create unresizeable fixed portait activity with min aspect ratio greater than parent
// aspect ratio.
final ActivityRecord sizeCompatActivity = new ActivityBuilder(mAtm)
.setTask(task).setResizeMode(RESIZE_MODE_UNRESIZEABLE)
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.setMinAspectRatio(targetMinAspectRatio).build();
// Resize display running unresizeable activity to make it enter size compat mode.
resizeDisplay(display, 1800, 1000);
final Rect sizeCompatAppBounds = new Rect(sizeCompatActivity.getConfiguration()
.windowConfiguration.getAppBounds());
// Check that aspect ratio of app bounds is equal to the min aspect ratio.
final float delta = 0.01f;
assertEquals(targetMinAspectRatio, ActivityRecord
.computeAspectRatio(fixedOrientationAppBounds), delta);
assertEquals(targetMinAspectRatio, ActivityRecord
.computeAspectRatio(minAspectRatioAppBounds), delta);
assertEquals(targetMinAspectRatio, ActivityRecord
.computeAspectRatio(sizeCompatAppBounds), delta);
}
private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
float letterboxHorizontalPositionMultiplier) {
// Set up a display in landscape and ignoring orientation request.

View File

@@ -19,7 +19,9 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -77,6 +79,7 @@ class TestDisplayContent extends DisplayContent {
private int mPosition = POSITION_BOTTOM;
protected final ActivityTaskManagerService mService;
private boolean mSystemDecorations = false;
private int mStatusBarHeight = 0;
Builder(ActivityTaskManagerService service, int width, int height) {
mService = service;
@@ -125,6 +128,10 @@ class TestDisplayContent extends DisplayContent {
Insets.of(0, height, 0, 0), null, new Rect(20, 0, 80, height), null, null);
return this;
}
Builder setStatusBarHeight(int height) {
mStatusBarHeight = height;
return this;
}
Builder setCanRotate(boolean canRotate) {
mCanRotate = canRotate;
return this;
@@ -158,6 +165,14 @@ class TestDisplayContent extends DisplayContent {
doReturn(false).when(displayPolicy).hasStatusBar();
doReturn(false).when(newDisplay).supportsSystemDecorations();
}
if (mStatusBarHeight > 0) {
doReturn(true).when(displayPolicy).hasStatusBar();
doAnswer(invocation -> {
Rect inOutInsets = (Rect) invocation.getArgument(0);
inOutInsets.top = mStatusBarHeight;
return null;
}).when(displayPolicy).convertNonDecorInsetsToStableInsets(any(), anyInt());
}
Configuration c = new Configuration();
newDisplay.computeScreenConfiguration(c);
c.windowConfiguration.setWindowingMode(mWindowingMode);