Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 224f43e8 authored by Shivam Agrawal's avatar Shivam Agrawal Committed by Automerger Merge Worker
Browse files

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

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15029660

Change-Id: I056dde15efa9be020a8525fe3254f9e118a132b1
parents b43ce546 a65bf9d3
Loading
Loading
Loading
Loading
+105 −85
Original line number Diff line number Diff line
@@ -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.
     */
    private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outBounds) {
     * @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 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);

        // 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;

        // 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;
        // 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);

        // 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;
        // Override from config_fixedOrientationLetterboxAspectRatio or via ADB with
        // set-fixed-orientation-letterbox-aspect-ratio.
        final float letterboxAspectRatioOverride =
                mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
        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);

        // 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;
    }

+55 −0
Original line number Diff line number Diff line
@@ -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.
+15 −0
Original line number Diff line number Diff line
@@ -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);