Merge "Respect App Requested Orientation on Close-to-Square Displays" into sc-v2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
caf03a848f
@@ -299,6 +299,7 @@ import android.view.InputApplicationHandle;
|
||||
import android.view.RemoteAnimationAdapter;
|
||||
import android.view.RemoteAnimationDefinition;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
import android.view.Surface.Rotation;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.SurfaceControl.Transaction;
|
||||
import android.view.WindowInsets.Type;
|
||||
@@ -780,6 +781,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
*/
|
||||
private final Configuration mTmpConfig = new Configuration();
|
||||
private final Rect mTmpBounds = new Rect();
|
||||
private final Rect mTmpOutNonDecorBounds = new Rect();
|
||||
|
||||
// Token for targeting this activity for assist purposes.
|
||||
final Binder assistToken = new Binder();
|
||||
@@ -7173,8 +7175,53 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation
|
||||
* change and the requested orientation is different from the parent.
|
||||
* In some cases, applying insets to bounds changes the orientation. For example, if a
|
||||
* close-to-square display rotates to portrait to respect a portrait orientation activity, after
|
||||
* insets such as the status and nav bars are applied, the activity may actually have a
|
||||
* landscape orientation. This method checks whether the orientations of the activity window
|
||||
* with and without insets match or if the orientation with insets already matches the
|
||||
* 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.
|
||||
*/
|
||||
private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outBounds) {
|
||||
if (mDisplayContent == null) {
|
||||
return true;
|
||||
}
|
||||
// Only need to make changes if activity sets an orientation
|
||||
final int requestedOrientation = getRequestedConfigurationOrientation();
|
||||
if (requestedOrientation == ORIENTATION_UNDEFINED) {
|
||||
return true;
|
||||
}
|
||||
// Compute parent orientation from bounds
|
||||
final int orientation = parentBounds.height() >= parentBounds.width()
|
||||
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
|
||||
// Compute orientation from stable parent bounds (= parent bounds with insets applied)
|
||||
final Task task = getTask();
|
||||
task.calculateInsetFrames(mTmpOutNonDecorBounds /* outNonDecorBounds */,
|
||||
outBounds /* outStableBounds */, parentBounds /* bounds */,
|
||||
mDisplayContent.getDisplayInfo());
|
||||
final int orientationWithInsets = outBounds.height() >= outBounds.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
|
||||
|| orientationWithInsets == requestedOrientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes bounds (letterbox or pillarbox) when either:
|
||||
* 1. The parent doesn't handle the orientation change and the requested orientation is
|
||||
* different from the parent (see {@link DisplayContent#setIgnoreOrientationRequest()}.
|
||||
* 2. The parent handling the orientation is not enough. This occurs when the display rotation
|
||||
* may not be enough to respect orientation requests (see {@link
|
||||
* ActivityRecord#orientationRespectedWithInsets}).
|
||||
*
|
||||
* <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied
|
||||
* in this method.
|
||||
@@ -7182,9 +7229,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig,
|
||||
int windowingMode) {
|
||||
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
|
||||
if (handlesOrientationChangeFromDescendant()) {
|
||||
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
|
||||
final Rect containerBounds = new Rect(parentBounds);
|
||||
boolean orientationRespectedWithInsets =
|
||||
orientationRespectedWithInsets(parentBounds, containerBounds);
|
||||
if (handlesOrientationChangeFromDescendant() && orientationRespectedWithInsets) {
|
||||
// No need to letterbox because of fixed orientation. Display will handle
|
||||
// fixed-orientation requests.
|
||||
// fixed-orientation requests and a display rotation is enough to respect requested
|
||||
// orientation with insets applied.
|
||||
return;
|
||||
}
|
||||
if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable()) {
|
||||
@@ -7204,7 +7256,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
// If the activity requires a different orientation (either by override or activityInfo),
|
||||
// make it fit the available bounds by scaling down its bounds.
|
||||
final int forcedOrientation = getRequestedConfigurationOrientation();
|
||||
if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) {
|
||||
if (forcedOrientation == ORIENTATION_UNDEFINED
|
||||
|| (forcedOrientation == parentOrientation && orientationRespectedWithInsets)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7216,53 +7269,75 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
return;
|
||||
}
|
||||
|
||||
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
|
||||
final int parentWidth = parentBounds.width();
|
||||
final int parentHeight = parentBounds.height();
|
||||
float aspect = Math.max(parentWidth, parentHeight)
|
||||
/ (float) Math.min(parentWidth, parentHeight);
|
||||
// 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);
|
||||
|
||||
// Override from config_fixedOrientationLetterboxAspectRatio or via ADB with
|
||||
// set-fixed-orientation-letterbox-aspect-ratio.
|
||||
final float letterboxAspectRatioOverride =
|
||||
mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
|
||||
aspect = letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
|
||||
? letterboxAspectRatioOverride : aspect;
|
||||
aspectRatio = letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
|
||||
? letterboxAspectRatioOverride : aspectRatio;
|
||||
|
||||
// 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 (aspect > maxAspectRatio && maxAspectRatio != 0) {
|
||||
aspect = maxAspectRatio;
|
||||
} else if (aspect < minAspectRatio) {
|
||||
aspect = minAspectRatio;
|
||||
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.
|
||||
Rect mTmpFullBounds = new Rect(resolvedBounds);
|
||||
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;
|
||||
if (forcedOrientation == ORIENTATION_LANDSCAPE) {
|
||||
final int height = (int) Math.rint(parentWidth / aspect);
|
||||
final int top = parentBounds.centerY() - height / 2;
|
||||
resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + height);
|
||||
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 {
|
||||
final int width = (int) Math.rint(parentHeight / aspect);
|
||||
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();
|
||||
final int left = width <= parentAppBounds.width()
|
||||
// Avoid overlapping with the horizontal decor area when possible.
|
||||
? parentAppBounds.left : parentBounds.centerX() - width / 2;
|
||||
resolvedBounds.set(left, parentBounds.top, left + width, parentBounds.bottom);
|
||||
resolvedBounds.set(parentAppBounds.left, parentBounds.top,
|
||||
parentAppBounds.left + activityWidth, parentBounds.bottom);
|
||||
}
|
||||
|
||||
if (mCompatDisplayInsets != null) {
|
||||
mCompatDisplayInsets.getBoundsByRotation(
|
||||
mTmpBounds, newParentConfig.windowConfiguration.getRotation());
|
||||
if (resolvedBounds.width() != mTmpBounds.width()
|
||||
|| resolvedBounds.height() != mTmpBounds.height()) {
|
||||
// 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()) {
|
||||
// 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.
|
||||
resolvedBounds.set(mTmpFullBounds);
|
||||
resolvedBounds.set(prevResolvedBounds);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -8507,6 +8582,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
* compatibility mode activity compute the configuration without relying on its current display.
|
||||
*/
|
||||
static class CompatDisplayInsets {
|
||||
/** The original rotation the compat insets were computed in */
|
||||
final @Rotation int mOriginalRotation;
|
||||
/** The container width on rotation 0. */
|
||||
private final int mWidth;
|
||||
/** The container height on rotation 0. */
|
||||
@@ -8533,6 +8610,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
/** Constructs the environment to simulate the bounds behavior of the given container. */
|
||||
CompatDisplayInsets(DisplayContent display, ActivityRecord container,
|
||||
@Nullable Rect fixedOrientationBounds) {
|
||||
mOriginalRotation = display.getRotation();
|
||||
mIsFloating = container.getWindowConfiguration().tasksAreFloating();
|
||||
if (mIsFloating) {
|
||||
final Rect containerBounds = container.getWindowConfiguration().getBounds();
|
||||
|
||||
@@ -2587,7 +2587,7 @@ class Task extends TaskFragment {
|
||||
* @param outStableBounds where to place bounds with stable insets applied.
|
||||
* @param bounds the bounds to inset.
|
||||
*/
|
||||
private void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
|
||||
void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
|
||||
DisplayInfo displayInfo) {
|
||||
outNonDecorBounds.set(bounds);
|
||||
outStableBounds.set(bounds);
|
||||
|
||||
@@ -2700,6 +2700,40 @@ public class ActivityRecordTests extends WindowTestsBase {
|
||||
assertFalse("Starting window should not be present", activity.hasStartingWindow());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseToSquareFixedOrientationPortrait() {
|
||||
// create a square display
|
||||
final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000)
|
||||
.setSystemDecorations(true).build();
|
||||
final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
|
||||
|
||||
// create a fixed portrait activity
|
||||
final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
|
||||
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
|
||||
|
||||
// check that both the configuration and app bounds are portrait
|
||||
assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
|
||||
assertTrue(activity.getConfiguration().windowConfiguration.getAppBounds().width()
|
||||
<= activity.getConfiguration().windowConfiguration.getAppBounds().height());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseToSquareFixedOrientationLandscape() {
|
||||
// create a square display
|
||||
final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000)
|
||||
.setSystemDecorations(true).build();
|
||||
final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
|
||||
|
||||
// create a fixed landscape activity
|
||||
final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
|
||||
.setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE).build();
|
||||
|
||||
// check that both the configuration and app bounds are landscape
|
||||
assertEquals(ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation);
|
||||
assertTrue(activity.getConfiguration().windowConfiguration.getAppBounds().width()
|
||||
> activity.getConfiguration().windowConfiguration.getAppBounds().height());
|
||||
}
|
||||
|
||||
private void assertHasStartingWindow(ActivityRecord atoken) {
|
||||
assertNotNull(atoken.mStartingSurface);
|
||||
assertNotNull(atoken.mStartingData);
|
||||
|
||||
@@ -395,6 +395,7 @@ public class SizeCompatTests extends WindowTestsBase {
|
||||
assertFitted();
|
||||
|
||||
final Rect currentBounds = mActivity.getWindowConfiguration().getBounds();
|
||||
final Rect currentAppBounds = mActivity.getWindowConfiguration().getAppBounds();
|
||||
final Rect originalBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
|
||||
|
||||
final int notchHeight = 100;
|
||||
@@ -422,8 +423,8 @@ public class SizeCompatTests extends WindowTestsBase {
|
||||
// Because the display cannot rotate, the portrait activity will fit the short side of
|
||||
// display with keeping portrait bounds [200, 0 - 700, 1000] in center.
|
||||
assertEquals(newDisplayBounds.height(), currentBounds.height());
|
||||
assertEquals(currentBounds.height() * newDisplayBounds.height() / newDisplayBounds.width(),
|
||||
currentBounds.width());
|
||||
assertEquals(currentAppBounds.height() * newDisplayBounds.height()
|
||||
/ newDisplayBounds.width(), currentAppBounds.width());
|
||||
assertFitted();
|
||||
// The appBounds should be [200, 100 - 700, 1000].
|
||||
final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds();
|
||||
|
||||
Reference in New Issue
Block a user