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

Commit a65bf9d3 authored by Shivam Agrawal's avatar Shivam Agrawal Committed by Android (Google) Code Review
Browse files

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

parents 7ebbcf16 1f790e33
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);